-
useState
대신 useObserver
를 생성
-
setState
했던 부분은 notify
로 전파
-
state
를 사용했던 부분은 observe
로 상태 전파 받은 뒤 내부의 useState
를 변경
import { useCallback, useMemo } from "react";
export type Observer<T> = (payload: T) => void;
export type Observe<T> = (observer: Observer<T>) => () => void;
export const useObserver = <T>() => {
const observers: Set<Observer<T>> = useMemo(() => new Set(), []);
const observe = useCallback((observer: Observer<T>) => {
observers.add(observer);
return () => {
observers.delete(observer);
};
}, []);
const notify = useCallback((payload: T) => {
observers.forEach((observer) => observer(payload));
}, []);
return {
observe,
notify,
};
};
- Context API 단점을 보완하기 위해 만듬
import { useCallback, useMemo, startTransition } from "react";
export type Observer<T> = (payload: T) => void;
export type Observe<T> = (observer: Observer<T>) => () => void;
export const useObserver = <T>() => {
const observers: Set<Observer<T>> = useMemo(() => new Set(), []);
const priorityObservers: Set<Observer<T>> = useMemo(() => new Set(), []);
const observe = useCallback((observer: Observer<T>) => {
observers.add(observer);
return () => {
observers.delete(observer);
};
}, []);
const observePriority = useCallback((observer: Observer<T>) => {
priorityObservers.add(observer);
return () => {
priorityObservers.delete(observer);
};
}, []);
const notify = useCallback((payload: T) => {
priorityObservers.forEach((observer) => {
observer(payload);
});
observers.forEach((observer) => {
startTransition(() => {
observer(payload);
});
});
}, []);
return {
observe,
observePriority,
notify,
};
};
type UseObserverStateProps<T, U> = {
observe: Observe<T>;
mapper: (state: T) => U;
deps?: unknown[];
initState?: T;
};
export const useObserverState = <T, U>({
observe,
mapper,
deps,
initState,
}: UseObserverStateProps<T, U>) => {
const [state, setState] = useState<U>();
useLayoutEffect(() => {
if (initState) {
setState(mapper(initState));
}
return observe((state) => {
setState(mapper(state));
});
}, deps);
return state;
};
export type TMyContext = {
initState: State;
dispatch: React.Dispatch<Action>;
observe: Observe<State>;
observePriority: Observe<State>;
};
const MyContext = createContext<TMyContext | null>(null);
function MyContextProvider(
params: Partial<Pick<TMyContext, "initState">> & {
children: React.ReactNode;
},
) {
const { observe, observePriority, notify } = useObserver<State>();
const stateRef = useRef(initialState);
const dispatch = (action: Action) => {
stateRef.current = reducer(stateRef.current, action);
notify(stateRef.current);
};
return (
<MyContext.Provider
value={{
initState: stateRef.current,
dispatch,
observe,
observePriority,
}}
>
{params.children}
</MyContext.Provider>
);
}
const Posts = () => {
const { initState, observe } = useMyContext();
const posts = useObserverState({
initState,
observe,
mapper: (state) => {
return state.posts;
}
});
return (
<div>
{posts.map((post) => <div>{post}</div>)}
</div>
)
};