카카오스타일이 상태 관리 라이브러리 Jotai 를 실제로 어떻게 쓰고 있는지 패턴별로 정리한 글이다. 원시 atom 과 파생 atom, 쓰기 전용 atom 으로 만드는 비즈니스 로직, Provider 를 이용한 초기값 주입, 그리고 뷰와 분리된 store 를 테스트하는 방법까지 다룬다.
핵심 포인트- 기본 사용은 useState 와 다를 게 없지만, 하위 컴포넌트에서 같은 상태에 접근해야 할 때 차이가 난다.
- 읽기나 쓰기 중 하나만 필요할 때 쓰는 유틸리티 함수가 따로 있고, 쓰기 함수는 useState 의 setState 처럼 이전 상태를 받아 쓸 수 있다.
- 파생 atom 은 MobX 나 Vue 의 computed 와 비슷하며 useMemo 처럼 의존 atom 이 바뀔 때만 재계산한다.
- 쓰기 전용 atom 으로 비즈니스 로직을 정의한다. 예로 전화번호 인증 과정에서 인증이 끝났으면 번호를 바꾸지 못하게 하는 규칙을 넣는다.
- 비동기 로직도 자연스럽다. Redux 라면 미들웨어가 필요한 서버 API 호출을 Jotai 에서는 atom 안에 그대로 정의할 수 있다.
상세 정리- 값과 액션은 사실 같은 atom 이지만 편의상 구분하며, 둘을 묶은 이름을 고민하다 Redux 나 MobX 를 따라 store 라고 부르기로 했다. ViewModel 도 후보였다.
- MobX 처럼 값과 액션을 한 클래스에 담으면 파일을 나누기 어려운데, Jotai 는 atom 의 모음이라 나누기 편하다.
- 디렉터리 구조는 값을 atoms 에, 액션을 actions 에 둔다. atoms 는 연관성 높은 것끼리 한 파일로 묶고, 액션은 보통 길어서 액션별로 파일을 만든다.
- atom 값은 전역에 있는 게 아니라 Context 처럼 컴포넌트 트리 위에 저장된다. Provider 를 지정하지 않으면 기본 저장소가 쓰이고, 참조하는 Provider 가 다르면 같은 atom 을 써도 값이 다르다.
- 페이지와 내부 구성 컴포넌트가 분리돼 있어 스토리북에서도 정상 동작하도록 Provider 선언을 내부 컴포넌트 쪽에 둔다.
- 외부에서 props 로 받은 값을 initialValues 로 atom 에 넣으면 이후에는 props 를 참조하지 않고 atom 에서 읽을 수 있다.
- atom 정의를 하위 디렉터리에서 하므로 초기화 코드도 정의와 함께 두는 편이 바람직해, 초기화 내용을 분리해 atoms 인덱스에 만든다.
- 고정값 대신 처음 사용되는 시점에 값을 정하게 할 수도 있다. localStorage 에서 읽어 초기화하는 경우가 그렇다.
- 다만 그것만으로는 값을 갱신했을 때 localStorage 에 반영되지 않는다. 쓰기 시 커스텀 동작을 하면서 저장도 하려면 저장용 atom 을 분리해야 한다.
- 관련 유틸 하나는 일부 환경에서 오동작하는 듯해 쓰지 못하고 있고, Promise 로 비동기 동작하는 부분을 의심한다고 적는다.
- 개별 atom 의 값을 합친 것이 필요하면 파생 atom 으로 만들고, 반대로 전체 값 중 일부만 필요하면 선택 유틸을 쓴다. 원본을 바꿀 일이 없을 때 편리하다.
- 여러 페이지가 쓰는 공통 컴포넌트가 제공하는 상태를 atom 으로 정의해 부모가 읽게 할 수 있다.
- 이때 공용 컴포넌트에 Provider 를 따로 두지 않았는데, 그래서 인스턴스를 여러 개 만들 수는 없다. Input 같은 컴포넌트에는 쓸 수 없는 방식이다.
- 테스트는 상태를 뷰와 분리한 덕을 본다. Jotai store 는 뷰와 무관하므로 액션 단위 테스트를 액션 파일과 같은 디렉터리에 두고 작성한다.
- 예시는 포인트 최대 적용 액션이고, 사용 가능한 금액이 주문 금액보다 많으면 주문 금액만큼만 적용된다는 규칙을 검증한다.
왜 읽나Jotai 를 써보긴 했는데 프로젝트 규모가 커지며 구조가 흔들리는 팀에게, 파일 분리부터 Provider 초기화와 테스트까지 실제 운영 패턴을 통째로 보여주는 레시피.