1. Action을 메소드로 처리하기
🔗 실습 링크
💡 Redux와 달리 단일 스토어가 아니기에, 스토어 여러 개를 만들어서 사용하면 됨
CounterStore, useCounterStore 만들기
// src/stores/ObjectStore.ts
type Listener = () => void;
export default class ObjectStore {
private listeners = new Set<Listener>();
addListener(listener: Listener) {
this.listeners.add(listener);
}
removeListener(listener: Listener) {
this.listeners.delete(listener);
}
protected publish() {
this.listeners.forEach((listener) => listener());
}
}
// src/stores/CounterStore.ts
import {singleton} from 'tsyringe';
import ObjectStore from './ObjectStore';
@singleton()
export default class CounterStore extends ObjectStore {
count = 0;
/**
* 카운트를 증가시킨다.
*/
increase(step = 1) {
this.count += step;
this.publish();
}
/**
* 카운트를 감소시킨다.
*/
decrease() {
this.count -= 1;
this.publish();
}
}
// src/hooks/useObjectStore.ts
import {useEffect} from 'react';
import useForceUpdate from './useForceUpdate';
import ObjectStore from '../stores/ObjectStore';
export default function useObjectStore<T extends ObjectStore>(store: T): T {
const forceUpdate = useForceUpdate();
useEffect(() => {
store.addListener(forceUpdate);
return () => store.removeListener(forceUpdate);
}, [store, forceUpdate]);
return store;
}
// src/hooks/useCounterStore.ts
import {container} from 'tsyringe';
import useObjectStore from './useObjectStore';
import CounterStore from '../stores/CounterStore';
export default function useCounterStore() {
const store = container.resolve(CounterStore);
return useObjectStore(store);
}
2. usestore-ts
usestore-ts
🔗 실습 링크
React state management library
💡 내부적으로 어떻게 동작하는지 이해하고 사용할 것
타입스크립트를 잘 모른다면 어려울 수 있음
남에게 설명할 때 내부적으로는 이런 동작을 하고 있으며 어떤 방식으로 작동하는지 얘기할 수 있어야 함
사용 방법
패키지 설치
npm install usestore-ts
tsconfig.json
파일 decorators 사용 옵션 변경
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
Store 생성
// CounterStore.ts
import {singleton} from 'tsyringe';
import {Store, Action} from 'usestore-ts';
@singleton()
@Store()
export default class CounterStore {
count = 0;
@Action()
increase(step = 1) {
this.count += step;
}
@Action()
decrease() {
this.count -= 1;
}
}
Custom Hook 작성
// useCounterStore.ts
import {container} from 'tsyringe';
import {useStore} from 'usestore-ts';
import CounterStore from '../stores/CounterStore';
export default function useCounterStore() {
const store = container.resolve(CounterStore);
return useStore(store);
}
사용 방법 예시
const [{ count }, store] = useStore(counterStore);
첫번째 인자로 스토어의 값, 두번째 인자로 스토어 자체가 반환
불러와서 사용하기
// Counter.tsx
export default function Counter() {
const [{count}] = useCounterStore();
// 내용
}
// CounterControl.tsx
export default function CounterControl() {
const [, store] = useCounterStore();
// 내용
}
비동기 함수에 @Action 추가
⚠️ 비동기 함수에 @Action을 붙이면 다르게 작동할 수 있다는 점에 주의
별도의 액션을 만들면 신경 쓸 부분이 줄어듦
@singleton()
@Store()
class PostStore {
posts: Post[] = [];
// 여기에 @Action() 를 붙일 경우 프로미스를 리턴해서 publish
async fetchPosts() {
this.startLoading();
const posts = await fetchPosts();
this.completeLoading(posts);
}
@Action()
startLoading() {
this.posts = [];
}
@Action()
completeLoading(posts: Post[]) {
this.posts = posts;
}
}
Immer
Immer
불변성을 유지해주는 코드를 편리한 방식으로 작업할 수 있게 해주는 라이브러리
오브젝트 전체를 복사해서 주고, 그것을 이용할 수 있도록 되어 있음
Redux Toolkit 에도 기본으로 적용
import produce from "immer"
const nextState = produce(baseState, draft => {
draft[1].done = true
draft.push({title: "Tweet about it"})
})
3. useSyncExternalStore
useSyncExternalStore
FECONF 2022 - 상태관리 이 전쟁을 끝내러 왔다
external store를 구독할 수 있는 React Hook
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot ?)