0w0

reactHook을 10번 읽고서

reack hook에서 쓰는 JavaScript의 부분들

react hook 두서없이 정리

기본적으로 useState, useEffect에 다 하면 10개 + 커스텀 hook

왜?

hook은 호출 순서에 의존하기 때문

useState

useEffect

너무 많이 변경될 때?

useContext

useReducer

useState 대체함수

useCallBack, useMemo

useCallBack 콜백을 메모이제이션 useMemo 값을 메모이제이션

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useCallBack(fn, deps) === useMemo(() => fn, deps);

useMemo는 성능 최적화를 위해 사용할 수는 있지만 의미상으로 보장이 있다고 생각하지는 마세요. 문서에는 이런 설명이 있는데 덧붙여서 되도록 쓰지 않도록 권장하는 듯?

useRef

변경 가능한 객체를 반환하는 것 상자, 컨테이너 같은 것으로 보면 된다. 가변값 유지에 용이 변경되어도 따로 알려주지 않음 DOM 노드에 어떤 실행이 하고 싶으면 콜백 ref

useImperativeHandle, useLayoutEffect, useDebugValue

딱히 필요할 때가 있다기보다는 굳이 필요하면 쓰면 되는 내용

hook에 대한 감상

지금까지 useState, useEffect, useRef만 사용해봤는데, 기회가 있다면 useCallBack이랑 useDebugValue도 사용해보고 싶다

특징

클래스 사용하는 HOC, render props 대신 함수형 구조로 평탄하게, 테스트하기 쉽게 만들 수 있음

hook은 into hook(연동)을 의미

동기


추가

React 18에 새로운 Hook이 생겼음

추가 hook

Library Hooks

useDeferredValue

const deferredValue = useDeferredValue(value);

값을 받아, 더 긴급한 갱신에 defer 된 값의 새로운 복사본을 반환.

렌더링이 사용자 입력의 같이 긴급한 갱신의 결과라면, React는 이전 값을 반환, 긴급 렌더링 완료 후, 새로운 값을 렌더링한다.

debouncing, throttling를 사용한 갱신을 defer된 user-space hook과 닮았다. useDeferredValue를 사용하는 이점은 React가 다른 작업이 끝날 때, (임의 시간을 기다리지 않고서) 갱신 작업을 할 때, startTransition 같이 기존의 컨텐츠에 기대 않았던 fallback을 일으키지 않고 defer 값을 중단할 수 있습니다.

Memoizing deferred children

useDeferredValue는 전달된 값만을 defer함

긴급 갱신할 때 자식 컴포넌트의 재렌더링을 막고싶다면, React.memo, React.useMemo로 컴포넌트도 메모할 필요가 있음

function Typeahead() {
  const query = useSearchQuery('');
  const deferredQuery = useDeferredValue(query);

  // Memoizing은 deferredQuery가 변경될 때만 재 렌더링되도록 React에 지시
  // query가 변경될 때가 아님
  const suggestions = useMemo(
    () => <SearchSuggestions query={deferredQuery} />,
    [deferredQuery]
  );

  return (
    <>
      <SearchInput query={query} />
      <Suspense fallback="Loading results...">{suggestions}</Suspense>
    </>
  );
}

자식 요소를 메모하면 React에 deferredQuery가 변경될 때만, 재 렌더링할 필요가 있고, query가 변경될 때는 필요없다 전달합니다. 주의점은 useDeferredValue에 한정된 이야기가 아니라 debouncingthrottling를 사용시 이 hook과 같은 패턴이 됩니다.

useTransition

const [isPending, startTransition] = useTransition();

transition의 pending state를 보이는 stateful 값과 함수 시작을 반환

startTransition를 사용하면 제공된 콜백 함수 내에 갱신을 transition으로 마크할 수 있음

startTransition(() => {
  setCount(count + 1);
});

isPending으로는 transition 활성 등의 상황에서 pending state를 보이도록 할 수 있음

function App() {
  const [isPending, startTransition] = useTransition();
  const [count, setCount] = useState(0);

  function handleClick() {
    startTransition(() => {
      setCount((funcUpdate) => funcUpdate + 1);
    });
  }

  return (
    <div>
      {isPending && <Spinner />}
      <button onClick={handleClick}>{count}</button>
    </div>
  );
}

transition 갱신은 클릭 등 긴급성이 높은 갱신에 건내준다. transition 내의 갱신은 re-suspended 컨텐츠의 fallback를 보이지 않는다. 이에 사용자는 갱신을 렌더링하는 동안, 현재 컨텐츠와 interacting을 지속할 수 있음

useId

const id = useId();

useId는 서버-클라이언트 사이를 안정시킬 유일한 ID를 생성하기 위한 hook

기본 예는 id 필요하는 요소에 직접 전달

function Checkbox() {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>Do you like React?</label>
      <input id={id} type="checkbox" name="react" />
    </>
  );
}

같은 컴포넌트에 여러 ID가 필요하면 같은 ID 사용하고 suffix를 첨가

function NameFields() {
  const id = useId();
  return (
    <div>
      <label htmlFor={id + '-firstName'}>First Name</label>
      <div>
        <input id={id + '-firstName'} type="text" />
      </div>
      <label htmlFor={id + '-lastName'}>Last Name</label>
      <div>
        <input id={id + '-lastName'} type="text" />
      </div>
    </div>
  );
}

useId는 토큰을 포함한 문자열을 생성, 토큰의 유일성을 확보함에 도움되지만 CSS 셀렉터, querySelectorAll 같은 API는 지원되지 않는다. useId는 다중 루트 앱 충돌을 방지하기 위해 identifierPrefix를 지원한다. 설정하기 위해서 hydrateRoot, ReactDOMServer 옵션을 참조

Library Hooks

라이브러리 제작자가 라이브러리 React 모델에 도움이 되게 제공되고 있습니다. 일반적인 코드에는 사용하면 않습니다.

useSyncExternalStore

const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot]);

선택적 hydration, time slicing 같이 동시 렌더링 기능과 호환성 방법으로, 외부 자료 소스에서 읽어 구독하기 위한 hook입니다.

메서드는 스토어의 값을 반환, 3개의 인수를 받습니다.

가장 기본적인 예로는 단순 스토어 전체를 구독합니다.

const state = useSyncExternalStore(store.subscribe, store.getSnapshot);

또, 특정 분야를 구독하는 것도 가능합니다.

const selectedField = useSyncExternalStore(
  store.subscribe,
  () => store.getSnapshot().selectedField
);

서버 렌더링할 때는 서버에 사용하는 스토어 값을 serialize해서, useSyncExternalStore에 제공하는 필요가 있습니다. React는 hydration 사용해서 서버에서 미스매치를 방지합니다.

const selectedField = useSyncExternalStore(
  store.subscribe,
  () => store.getSnapshot().selectedField,
  () => INITIAL_SERVER_SNAPSHOT.selectedField
);

getSnapshot 캐시된 값을 반환할 필요가 있습니다. getSnapshot가 연속된 복수적으로 될 때, 스토어 간 갱신이 없다면, 반드시 같은 값을 반환할 필요가 없습니다. 다양한 React 버전을 지원하기 위해 use-sync-external-store/shim로 공개하고 있는 shim이 제공되고 있습니다. shim을 이용하고 싶으면 useSyncExternalStore를 우선하며, 그렇지 않으면 user-space 구현에 fallback 합니다. use-sync-external-store/with-selector로 공개된 getSnapshot 결과를 메모하기 위한 자동 지원을 준비된 API 버전도 제공됩니다.

useInsertionEffect

useInsertionEffect(didUpdate);

시그니처는 useEffect와 같으며, 모든 DOM 변이 전에 동기적으로 동작합니다. useLayoutEffect로 레이아웃을 읽기 전에, DOM에 스타일을 주입하기 위해 이것을 사용합니다. hook은 스코프가 한정되어 있기에, hook은 refs에 접근하지 못하며, 갱신을 스케쥴하는 것도 안 됩니다.

:::caution useInsertionEffect는 css-in-js 라이브러리 제작자만 사용해야 합니다.

useEffect/useLayoutEffect를 사용해주세요. :::

볼 거리