2년이 넘게 전에 쓰인 글이니 감안해서 참고해주세요.. 다시보니 내용이 너무 수치스럽네요...😓
신버전 프로젝트는 이렇게 잡았다.
중요 모듈:
1. Typescript
2. mobx5
3. emotion
4. Storybook
그 외 모듈:
5. React-hook-form
6. framer-motion
7. lodash
8. 아키텍쳐
9. 느낀 점
1. Typescript
물론 지금도 Typescript가 없다면 코딩하기 너무나도 싫겠지만(특히 소형이 아닌 프로젝트에서는) 타입을 위한 일들이 정말 많아진다.
제품을 만드는 가장 큰 이유가 무엇인가? 회사의 매출과 유저에게 좋은 가치를 제공하기 위함이 아니던가.
타입 체킹을 더 Generic하게 하고 싶거나, 더 추상화된 기능을 만들때 타입스크립트는 끔찍한 면모를 보여준다.
물론 내가 실력이 부족한 것이 커서 그렇다. 하지만 오직 나만이 생각하는 것만은 아닐 것이다.
특정 부분은 타입 추론을 하고 나머지 부분은 특정 타입 내의 추론된 타입을 통한 또 다른 타입 추측을 하고... 이런 식으로 타입들을 쓰다보면 내가 뭐하는 건가 싶다.
다른 언어에 비해 타입적 자유도가 엄청난 것은 알겠으나.. 실제 사용될것도 아닌(기능이 아닌) 타입 만드는데 많은 시간을 쏟고 있는 나를 보면 회의감이 든다.
타 라이브러리나 기본 기능의 타입 추론이 약해서 동일 기능임에도 불구하고 따로 만드는 시간을 버리는 헛짓을 하기도 한다.
일을 위한 일을 하는데 커다란 일조를 하고 있는 타입스크립트. 러프하게 타입을 추측하고, 일차원적 타입 정의 정도만 하고 기능이나 열심히 만들껄... 하고 후회하고 있다. 다른 강타입 컴파일 언어처럼 강력한 타입 체킹, 제한과, 추론은 포기를 하는 방향이 더 좋았을 것 같다.
그리고 자바스크립트와 타입스크립트의 미묘한 엇나감들이 계속 신경쓰인다.
[Symbol.iterator]()가 ConstructorParameters에 없다고 에러 뜨는 것도 아직도 해결 못하고 있다.. //@ts-ignore걸고 쓰고있다..
이슈를 닫은거 보니 해결한거 같아서 물어봤는데 아직도 답장이 없다.. 그리고
얼마전에 발견한 아직도 해결 못한 정말 해결하고 싶은 문젠데... 내가 의도한건 static 메소드인 fromArr를 B가 상속받아서 new this 했을때 B가 나오기를 의도 했는데 저 const b: A 라고 나온 것처럼 타입스크립트에서 A 라고 인식을 해버린다.
근데 오른쪽에 로그에 나온 것처럼 자바스크립트에선 잘 B 클래스의 인스턴스로 생성이 되었으며 내 의도대로 된 것을 볼 수 있다.
2020.1.18
문제 2 관련 이슈를 찾음. 5년이 넘도록 해결되지 않은 이슈.
=> github.com/microsoft/TypeScript/issues/5863
Polymorphic "this" for static members · Issue #5863 · microsoft/TypeScript
When trying to implement a fairly basic, but polymorphic, active record style model system we run into issues with the type system not respecting this when used in conjunction with a constructor or...
github.com
Type 'ConstructorParameters' must have a '[Symbol.iterator]()' method that returns an iterator. · Issue #37923 · microsoft/
TypeScript Version: found in 3.8.3 but still present in Nightly Search Terms: ConstructorParameters, Symbol.iterator Code function instantiate any)>( const...
github.com
이 해결 못한 두 문제에 대해 아는 분 있으면 댓글로 남겨주시면 너무 감사할 것 같습니다.... ㅠ_ㅠ
암튼 이래서 타입스크립트 대신 자바스크립트를 쓸 일은 없겠지만... 고도화와 추상화를 해서 쓰기에는 자꾸 장벽이 생긴다.
2. mobx5
정말 버리고 싶은 아이지만 리덕스도 쓰기 싫어서 꾸역꾸역 쓰고 있다.
관찰이 되다가 안되다가 할때 어떤 코드가 문젠지 열심히 뒤지면서 찾아야하고 디스트럭쳐링할때 관찰이 끊기거나 props로 받는것들을 열심히 useAsObservableSource해주는 것도 싫다.
리덕스를 쓰자니 이해도가 적어서 지금 당장 빨리 만들어야하는 프로젝트에 여러모로 지장이 갈테니 이번엔 일단 mobx로 했다.
recoil을 보면서 기대하고 있긴 한데 약간 써봤을때 좋았지만 지금 쓰긴 시기 상조이고...
클래스 기반으로 전부 작성해서 OOP적으로 사용하고 글로벌 스토어 훅(useRepository라는 용어를 정했다)로 클래스별 싱글 인스턴스를 동적 할당할 수 있게 하니 단점들을 보안해서 그나마 좀 낫다.
Mobx5가 프록시로 작성되어 있는데 내가 프록시로 작성하는 자료형과 어떻게 어우러져 잘 작성해야할지 모르겠다.
mobx6 버전이 나왔다는데 어 음... observable 안붙혀도 되는건 좋은데 결국 constructor에 작성을 해야 하는구나... OTL
순수한 클래스 형태를 유지하고 싶은 내 꿈은 mobx를 쓴다면 이룰 수 없는 꿈일 것 같다.
3. Emotion
Styled 컴포넌트 방식으로만 짜는건 생산성에 지대한 문제를 불러 일으킨다.
특히나 자주 수정되는 변화무쌍한 환경에선 더욱더 그러하다.
전통의 CSS 방식이 훨신 유연하고 스타일링 속도가 엄청난 차이를 보인다.
보통의 프론트엔드 개발자가 스타일드 컴포넌트를 사용하여 열심히 퍼블리싱을 한다면... 전통 SCSS + BEM 방식의 퍼블리셔의 속도의 1/10도 안될수도 있다.
웹 에이전시에서 퍼블리셔들은 하루에 페이지를 10장씩 찍어내고 한다. 프론트엔드랍시고 엄청난 시간을 버려가며 퍼블리싱하는게 정말이지 너무 싫다.
그래서 각 처음에는 모든 태그별 Emotion CSS, CSS Compose, Styled로 만드는 방식으로 하다가 중간에 틀었다.
컴포넌트별 Emotion CSS를 사용하는 방식으로 바꿨고. 컴포넌트의 특정 단위나 상태의 CSS를 만들어 그 내부의 컴포넌트들에게는 클래스로 주는 하이브리드 방식을 택했다.
이러니까 생산성이 좀 나아졌다. 순수 SCSS로 하는 생산성은 따라올 수 없지만. 그 또한 여러 스타일링간의 충돌이 일어날 수 있는 문제와 BEM 방식으로 할 시 CSS클래스 이름 짓는 것도 일이라 이정도로 나름 만족한다.
4. Storybook
스토리북은... 뭐 딱히 말할 게 없다.
만족한다.
다만 프로젝트가 커지면서 점점 급속도로 느려지는데 이를 어케할지 걱정이다.
5. React-hook-form
기본적으로 언컨트롤드 방식을 지향하는 리엑트 라이브러리라 개인적으로 매우 센세이션했다.
직접 써보니 정말 편했고, 컨트롤 방식의 기존 컴포넌트들도 Controller를 사용함으로써 언컨트롤드 방식이랑 값을 통합하여 사용할 수 있다.
리엑트를 한다면 필수적인 라이브러리가 아닐까 ?
안타깝게도 약간의 러닝 커브가 있다. 그 러닝커브를 줄이고자 다이나믹한 예제들과 잘되어있는 문서들이 있다. 그러므로 매우 강추.
특히 직접적인 폼 컨트롤이 적고 벨리데이션 위주의 폼을 사용한다면 더욱 좋다.
허나 각종 이 세상에 없는 UI들이 출몰하는 세계에선 나름 또 일을 위한 일을 하고 있을 수도 있다...
6. Framer-motion
그 유명한 Prototype툴인 Framer에서 애니메이션 라이브러리를 냈다고 했을때 나는 열광했다.
그리고 써보니까... 음.. 어.. 어떻게 써야하지 ?
깃헙 스타를 우수수 받으며 단번에 떠오른건 알겠는데 예제가 너무 적다..
일단 공식 문서에 나와있는 정보로 간단한 애니메이션은 작성하고는 있는데(아코디언 같은) 엄청난 매직과 유려한 Easing은 너무 마음에 든다.
다만 복잡한 애니메이션은 차마.. 예제가 더 쌓이던 문서가 더 친절해지던 해야 할 것 같다.
7. lodash
이제는 거의 습관처럼 쓰고 있는 lodash... 인데 확실히 ECMA버전이 높은 문법을 쓰다보면 의존성이 많이 줄어들었다.
fp-ts와 fp-ts 서드파티 라이브러리들을 사용하고 싶은 마음이 굴뚝같다. 언제쯤 가능할까.
8. 아키텍쳐
레이어단을 Api, Controller, Service, Repository를 나누고 그 내부의 모든 자료형들은 DTO를 넘기는 방식으로 하였다.
밑에는 귀찮아서 걍 노션 내용 복붇 하겠따.
Api
- 서버와의 통신을 담당
- api types에만 의존 (그 외에 의존성을 가지면 안됨)
- request type은 response type에만 의존성을 가질 수 있음
- 데이터 변환 로직이 없으며 순수하게 서버와의 통신만을 담당
- 우리의 서비스 로직, DTO 등에 절대 의존성이 생기면 안됨
Controller
- 서버 - 프론트간의 데이터 변환, 핸들링을 담당
- api types와 DTO에 의존 (그 외에 의존성을 가지면 안됨)
- 데이터 변환을 담당하는 Adapter와 핸들링 메소드들로 구성되어 있음
- 모든 Response는 DTO로 뱉고, 모든 Request는 해당 DTO를 받아서 Api의 Request로 변환
DTO
- 각각의 객체 (Controller, Service에서 사용되어지는 기본 단위)
- 다른 DTO, api response type에 의존성을 가질 수 있음. (그 외에 의존성을 가지면 안됨)
- DTO 안에 여러 DTO 포함 가능. (ex: StudentWorkbook)
- 비즈니스 로직을 가지면 안됨. 대신 파생데이터(computed)는 가질 수 있음
- 글로벌한건 src최상위의 dto에, 특정 페이지 안에서만 쓰이는 dto면 그 폴더 안에 작성
- DTO들의 묶음(리스트, 객체)은 Collection DTO로 작성 Collection DTO는 묶음에 대한 기능을 제공
Service
- DTO들과 Controller를 최종적으로 사용하는 비즈니스 로직 담당
- dto, controller에 의존 (그 외에 의존성을 가지면 안됨)
- DTO와 Controller를 통해 실질적인 처리인 비즈니스 로직을 작성
- 비즈니스 로직은 전역적인 케이스가 적으므로 최대한 해당 page내부의 service 폴더에 작성
- Service는 UI를 모른다 생각하고 작성
Component
- UI로 뿌려질 컴포넌트들
- service, dto에 의존 (그 외에 의존성을 가지면 안됨)
- UI 적 로직은 최대한 내부의 useLocalStore로 작성, 혹은 Service에서의 비즈니스 처리 로직의 결과를 UI로 뿌려줌
- 각 페이지는 하나의 Template를 가지고 있으며 해당 페이지의 진입점인 컴포넌트(index.tsx 격인)는 그 하위 컴포넌트의 데이터 의존성을 파악하여 해당 의존하고 있는 데이터가 없을시 아예 그 컴포넌트를 뿌려주지 말아야 할 책임이 있음.
Repository
- 전역 공간 (1. 각각의 정보들을 전역으로 저장하여 props drill을 피하고, 2. 마지막 행동과 상태를 기억하기 위함)
- CommonRepository: DTO를 저장하는 정적인 전역 공간. 정해진 값들이 들어감.
- ServiceRepository: Service를 저장하는 동적인 전역 공간. 각 Service 클래스를 식별자로 해서 해당 클래스의 인스턴스를 저장함. (각 Service별 1:1 관계)
- DynamicRepository: Service를 저장하는 동적에 동적인 전역 공간. 각 Service 클래스와 id를 두번째 식별자로 하여 해당 클래스의 인스턴스를 저장함. (각 Service별 1:N 관계)
프로젝트 초반에 작성한거라 지금이랑 달라진 내용들도 많을 것이다.
네이밍이 햇갈릴 수 있는데 백엔드(스프링)이랑 같은 용어를 사용하면서도 의미는 다른게 많다.
적용시키면서... 이제 중반까지 오면서 느낀 바로는 묶음을 도메인 묶음으로 했어야 한다는 것이다.
예를들면 controller 폴더에 컨트롤러들이 모여있는게 아니라, api에 api들이 모여있는게 아니라..
Student 폴더에 Student DTO, student.controller.ts, student.api.ts 등이 같이 모여있는 형태로 해야 훨신 쓸데없는 프로젝트 파일찾기 동선낭비를 줄일 수 있다.
그리고 DDD의 특성상에도 더욱 맞을 것이다.
그리고 typescript로 완전한 OOP를 적용시키는게 비효율적이라는 것이다.
원형이 되는 javascript의 특성상 조금 더 Functional Programming에 적합하다. 정확히 따지자면 뭐든 다 이도저도 아니지만.
그 이도저도 아닌 특성을 활용해서 오히려 너무 OOP, 너무 Functional 적으로 짜려고 하지 말고 러프하게 적용하며 생산성을 높이는게 훨 나을거같다.
각 모든 Iterable들을 통합하고 Iterable한 자료형들의 원형이 되어줄 Sequence를 정의하지 않았던게 아쉽다.
또 하나는 프론트엔드는 결국 프론트가 중심이다.
그렇기 때문에 내부 로직, 아키텍쳐가 중심이 아니라 결국 컴포넌트(화면)가 중심이 되어야 할 것이다.
이번 프로젝트에서 크게 후회되는 것 중 하나이다..
화면이 중심이고 화면은 늘 변화하는 친구이기 때문에 자꾸 틀을 만드려고 하면 나중에 열심히 자기가 만든 틀을 자기 손으로 부수고 있는 모습을 볼 수 있다. ^^;;
😥
9. 느낀점
CTO님이 오시면서 지금까지 이 회사에 와서는 크게 압박받지 않았던 시간 압박을 받고 있다.
확실히 시간 중심으로 생각하다보니... 내가 지금까지 뭘 해왔던건가 싶기도 하다.
좋은 개발을 하면 회사를 위한다는 것은 위선이며 자기만족이였다.
회사 입장에서 좋은 개발자는 원하는 기능을 빠르고 퀄리티있게 만들어주는 개발자이지 새로운 아키텍쳐와 Github에 뜨고 있는 기술들을 내부에 적용하고 있는 개발자가 아니다.
유지보수성이 높고 재사용성이 높다고? 하지만 그 유지보수성과 재사용성을 위해 일을 위한 일들이 많아진다면 결국 회사 입장에서는 추후 유지보수 비용까지 계산 하더라도 손해인데다 + 나중에 기존껀 부수고 어짜피 새로운 스택으로 또 개발할 것이니 의미 없는 짓이 되어버릴 수 있다. 프론트엔드는 특히나 그렇다.
그러니 프론트엔드쪽은 재빠르게 개발한 후 사용자 테스트를 거쳐 좋은 UIUX를 디벨롭하는 것이 훨신 이로울것이다.
우리가 개발을 하는 이유는 유저에게 좋은 사용자 경험을 주고 그로 인해 회사에서 수익을 얻기 위함이다.
그러니 우리는 개발을 할 때에도 비즈니스적 관점도 중요시 생각하여 개발을 해야 하는 것이 좋을 것이다.
물론 위의 예기는 회사 입장에서의 얘기이고 나름대로 개발자 본인의 자기만족을 쪼금씩 챙기도록... ㅎ,.ㅎ
미래는 Rust wasm...
'개발 > typescript, web' 카테고리의 다른 글
value change + onChange trigger (0) | 2021.05.11 |
---|---|
typescript) forwardRef + 컴포넌트 내부 useRef (2) | 2021.03.11 |
Typescript) Map Type 타입추론하여 Key, Value Type 추출 (0) | 2020.06.09 |
6개월 간의 React 프로젝트에서 mobx를 사용한 삽질기. (1) | 2020.02.21 |
CRA3 에서 craco + Mobx + React-hot-loader 적용하기 (0) | 2020.01.18 |