react 부분 렌더링 - ChoDragon9/posts GitHub Wiki

  1. useState 대신 useObserver를 생성
  2. setState했던 부분은 notify로 전파
  3. state를 사용했던 부분은 observe로 상태 전파 받은 뒤 내부의 useState를 변경

useObserver

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,
  };
};

useObserverState

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;
};

사용 코드

Context API 정의 부분

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>
  );
}

Context API 사용 부분

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>
  )
};
⚠️ **GitHub.com Fallback** ⚠️