useSyncExternalStore - 과소평가된 React API

20026 단어 react

useSyncExternalStore - 과소평가된 React API



외부 데이터 소스를 구독하기 위한 새로운 React 18 후크인 useSyncExternalStore() 에 대해 들어보셨을 것입니다. 선택기 시스템을 구현하기 위해 Redux과 같은 상태 관리 라이브러리에서 내부적으로 자주 사용됩니다.

그러나 자신의 애플리케이션 코드에서 useSyncExternalStore()를 사용하는 것은 어떻습니까?

이 대화형 기사에서는 불필요한 재렌더링을 트리거하는 초과 반환 React 후크라는 문제를 제시하고자 합니다. useSyncExternalStore()가 어떻게 좋은 해결책이 될 수 있는지 살펴보겠습니다.



💡 Pro tip: read the original article on ThisWeekInReact.com: it is interactive! 😜



오버 리턴 후크



React-Router에서 useLocation() 의 문제를 설명하겠습니다.

이 후크는 많은 속성( pathname , hash , search ...)이 있는 객체를 반환하지만 모든 속성을 읽지 못할 수도 있습니다. 후크를 호출하기만 하면 이러한 속성이 업데이트될 때 다시 렌더링이 트리거됩니다.

이 앱을 고려해 보겠습니다.

function CurrentPathname() {
  const { pathname } = useLocation();
  return <div>{pathname}</div>;
}

function CurrentHash() {
  const { hash } = useLocation();
  return <div>{hash}</div>;
}

function Links() {
  return (
    <div>
      <Link to="#link1">#link1</Link>
      <Link to="#link2">#link2</Link>
      <Link to="#link3">#link3</Link>
    </div>
  );
}

function App() {
  return (
    <div>
      <CurrentPathname />
      <CurrentHash />
      <Links />
    </div>
  );
}




해시 링크를 클릭하면 CurrentPathname 속성 😅을 사용하지 않더라도 hash 구성 요소가 다시 렌더링됩니다.

💡 Whenever a hook returns data that you don't display, think about React re-renders. If you don't pay attention, a tiny useLocation() call added at the top of a React tree could harm your app's performance.

ℹ️ The goal is not to criticize React-Router, but rather to illustrate the problem. useLocation() is just a good pragmatic candidate to create this interactive article. Your own React hooks and other third-party libraries might also over-return.



구조에 useSyncExternalStore?



official documentation 말한다:

useSyncExternalStore is a hook recommended for reading and subscribing from external data sources in a way that’s compatible with concurrent rendering features like selective hydration and time slicing.
This method returns the value of the store and accepts three arguments:

  • subscribe: function to register a callback that is called whenever the store changes.
  • getSnapshot: function that returns the current value of the store.
  • getServerSnapshot: function that returns the snapshot used during server rendering.


function useSyncExternalStore<Snapshot>(
  subscribe: (onStoreChange: () => void) => () => void,
  getSnapshot: () => Snapshot,
  getServerSnapshot?: () => Snapshot
): Snapshot;


약간 추상적인 느낌입니다. 이것은beta doc page 좋은 예를 제공합니다.

function subscribe(callback) {
  window.addEventListener("online", callback);
  window.addEventListener("offline", callback);
  return () => {
    window.removeEventListener("online", callback);
    window.removeEventListener("offline", callback);
  };
}

function useOnlineStatus() {
  return useSyncExternalStore(
    subscribe,
    () => navigator.onLine,
    () => true
  );
}

function ChatIndicator() {
  const isOnline = useOnlineStatus();
  // ...
}


브라우저 기록도 외부 데이터 소스로 간주될 수 있음이 밝혀졌습니다. React-Router와 함께 useSyncExternalStore를 사용하는 방법을 알아봅시다!

useHistorySelector() 구현하기



React-Router는 연결에 필요한 모든 것을 노출합니다useSyncExternalStore.
  • useHistory() 으로 브라우저 기록에 액세스
  • history.listen(callback) 의 기록 업데이트 구독
  • history.location 을 사용하여 현재 위치의 스냅샷에 액세스

  • ⚠️ This website uses React-Router v5: the solution will be different for React-Router v6 ( ).


    useHistorySelector()의 구현은 비교적 간단합니다.

    function useHistorySelector(selector) {
      const history = useHistory();
      return useSyncExternalStore(history.listen, () =>
        selector(history)
      );
    }
    


    우리 앱에서 사용해봅시다:

    function CurrentPathname() {
      const pathname = useHistorySelector(
        (history) => history.location.pathname
      );
      return <div>{pathname}</div>;
    }
    
    function CurrentHash() {
      const hash = useHistorySelector(
        (history) => history.location.hash
      );
      return <div>{hash}</div>;
    }
    




    이제 위의 해시 링크를 클릭하면 CurrentPathname 구성 요소가 더 이상 다시 렌더링되지 않습니다!

    다른 예: scrollY



    우리가 구독할 수 있는 외부 데이터 소스가 너무 많고 자체 선택기 시스템을 구현하면 React 리렌더링을 최적화할 수 있습니다.

    예를 들어 페이지의 scrollY 위치를 사용하려고 한다고 가정해 보겠습니다. 이 사용자 지정 React 후크를 구현할 수 있습니다.

    // A memoized constant fn prevents unsubscribe/resubscribe
    // In practice it is not a big deal
    function subscribe(onStoreChange) {
      global.window?.addEventListener("scroll", onStoreChange);
      return () =>
        global.window?.removeEventListener(
          "scroll",
          onStoreChange
        );
    }
    
    function useScrollY(selector = (id) => id) {
      return useSyncExternalStore(
        subscribe,
        () => selector(global.window?.scrollY),
        () => undefined
      );
    }
    


    이제 선택적 선택기와 함께 이 후크를 사용할 수 있습니다.

    function ScrollY() {
      const scrollY = useScrollY();
      return <div>{scrollY}</div>;
    }
    
    function ScrollYFloored() {
      const to = 100;
      const scrollYFloored = useScrollY((y) =>
        y ? Math.floor(y / to) * to : undefined
      );
      return <div>{scrollYFloored}</div>;
    }
    




    페이지를 스크롤하여 위의 구성요소가 어떻게 다시 렌더링되는지 확인하시겠습니까? 하나는 다른 것보다 덜 렌더링됩니다!

    💡 When you don't need a scrollY 1 pixel precision level, returning a wide range value such as scrollY can also be considered as over-returning. Consider returning a narrower value.
    For example: a useResponsiveBreakpoint() hook that only returns a limited set of values (small, medium or large) will be more optimized than a useViewportWidth() hook.
    If a React component only handles large screens differently, you can create an even narrower useIsLargeScreen() hook returning a boolean.



    결론



    이 기사가 useSyncExternalStore() 를 다시 살펴보게 되기를 바랍니다. 나는 이 훅이 현재 React 생태계에서 잘 사용되지 않고 있으며 좀 더 주의를 기울일 필요가 있다고 생각합니다. 구독할 수 있는 많은 외부 데이터 원본이 있습니다.

    아직 React 18로 업그레이드하지 않았다면 현재 이전 버전에서 이미 사용할 수 있는 npmuse-sync-external-store shim이 있습니다. 기본이 아닌 메모화된 값을 반환해야 하는 경우를 대비한 내보내기use-sync-external-store/with-selector도 있습니다.


    이와 같은 기사를 더 보려면 내 뉴스레터This Week In React를 구독하십시오.

    좋은 웹페이지 즐겨찾기