리엑트에서 state 관리를 위한 전략은 여러가지입니다.
가장 널리 알려진 redux,
useState hooks를 이용한 각 컴포넌트 레벨에서의 관리,
contextAPI 를 이용한 컴포넌트들 간의 state 관리,
그리고 mobx를 이용한 관리 등등.
저는 redux, redux-saga를 이용해 api call에 대한 side effect를 관리해 본 경험이 있는데요,
최근에는 mobx를 사용하며 redux보다 보다 편리하고 빠르게 state 관리를 하고 있습니다.
Mobx를 사용하는데 있어서 크게 두 가지로 나뉘어 볼 수 있을 것 같습니다.
1. 클래스형식의 state 관리,
2. useLocalStore, 즉 hooks를 이용한 관리
오늘은 클래스 형식의 관리를 다뤄보려고 합니다.
mobx-react-lite, mobx, lorem picsum api
yarn add mobx mobx-react-lite
lorem picsum api는 아래 사이트에서 확인하실 수 있습니다.
//store/rootstore.ts
import { observable } from 'mobx';
class RootStore {
@observable authToken: string = '';
export default RootStore;
본 예제에서 jwt 토큰 구현과 같은 유저 인증은 다루지 않습니다.
루트 스토어는 말 그대로 여러 store를 포함하고 접근할 수 있는 스토어의 모체입니다.
예를 들어 picture store, shopping store와 같은 여러 store가 존재한다고 생각해보면 이러한 서브 스토어들은 루트 스토어를 extends 해야 합니다.
lorem picsum api는 더미 사진 데이터를 response로 보내줍니다. 이 데이터들을 state 안에서 관리할 수 있게 하는 picList store를 만들어보겠습니다.
//rootstore/picList.ts
import RootStore from 'store/rootstore';
export type PictureList = {
id: string;
author: string;
height: number;
url: string;
download_url: string;
}
class PicListStore extends RootStore {
@observable picList: PictureList[] // 받을 더미데이터 리스트
@observable page: number;//요청할 데이터의 페이지
@Observable limit: number;//한 페이지에 받을 더미데이터 개수
constructor(rootStore: RootStore) {
super();
this.rootStore = rootStore;
}
@action.bound
async fetchImages() {
try{
this.onRequest();
const [, ListResponse ] = await requestList({page: this.page, limit: 10});
ListResponse ? this.pictureList = [...this.pictureList, ...ListResponse] : '';
this.page++;
}catch(error){
this.onFailure(error);
}
this.onSuccess();
}
}
export default PicListStore;
한번살펴볼까요? 위의 데코레이터 사용을 위해서는 tsconfig.ts와 .babelrc에 추가 설정을 해줘야 합니다. 해당 설정에 대한 설명은 아래 포스팅을 참조해주세요!
jay의 next.js config 설정방법
observable 데코레이터는 앱 구동환경에서 변경되며 관리할 state에 대해 표시를 해줍니다.
action 데코레이터를 통해 해당 함수에서 state가 변경되는 것을 말해줄 수 있습니다.
꼭 데코레이터를 사용하지 않아도 됩니다 :) 데코레이터를 처음 접하시는 분은 해당 표현에 너무 부담을 갖지 마시고 공식 문서를 한번 읽어보시는게 도움이 되실 거라고 생각합니다.
위와 같이 만든 클래스를 한번 함수형 컴포넌트에서 사용해볼까요?
//store/index.tsx
import {useLocalStore} from 'mobx-react-lite';
import React, {FC, createContext, useContext } from 'react';
import PicListStore from './picList';
export type RootStoreType = {
picList: PicListStore
}
// key 형식으로 원하는 스토어를 선택해서 사용할 수 있습니다.
export type StoreKeys = keyof RootStore;
//어느 컴포넌트에서도 스토어를 호출해 사용할 수 있도록 contextAPI를 사용할 것입니다.
const storeContext = createContext<RootStore | null>(null);
// 앱에 RootStore가 존재하지 않는다면 초기화시켜줍니다.
const initRootStore = (): RootStore => {
const rootStore: RootStore = {} as RootStore;
rootStore.picList = new PictureListStore(rootStore);
return rootStore;
}
//contextAPI Provider
export const StoreProvider: FC = ({ children }) => {
const store = useLocalStore(initRootStore);
return <storeContext.Provider value={store}>{children}</storeContext.Provider>
}
//
export function useStore<K extends StoreKeys>(storeName: K): RootStore[K]{
const store = useContext(storeContext);
if(!store) throw new Error('해당 useStore 훅은 StoreProvider 안에서 사용되어야 합니다! ');
return store[storeName];
}
위 storeProvider FC에 useLocalStore를 사용했습니다.
useLocalStore는 훅에서 mobx를 사용할 수 있게 해주며
contextAPI와 함께 어느 함수 컴포넌트에서라도 내가 필요한 스토어를 사용할 수 있습니다.
import React, {FC} from 'react';
import {useStore} from 'store';
const UseStoreExample: FC = () => {
const getPictureList = useStore("picList");
const { fetchImages, pictureList} = getPictureList;
return(
<>
...
</>
)
}
멋지네요.
앞서 PicListStore 에서 만들었던 fetchImages 함수와 pictureList state를 앱 전체 내에서 자유롭게 불러서 사용할 수 있게 되었습니다.
공유는 꼭 출처를 남겨주세요
감사합니다.