Closed13

React の組み込み hook をおさらいするぜ

タピオカタピオカ

useState

状態とその更新関数を提供する。

const [state, setState] = useState(initialState);

setState で状態を更新すると、コンポーネントが再レンダリングされる。
更新方法は2つある。

  • 新しい値を渡す
  • 前の値を受け取り新しい値を返す関数を渡す
const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <>
      Count: {count}
      {/* 新しい値を渡す */}
      <button onClick={() => setCount(0)}>Reset</button>
      {/* 前の値を受け取り新しい値を返す関数を渡す */}
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>
        Increment
      </button>
    </>
  );
};
タピオカタピオカ

useEffect

データの購読やタイマーなどの副作用を扱うためのフック。

useEffect(didUpdate);

副作用はレンダリングの完了時に実行される。
副作用でクリーンアップ用関数を返すと、コンポーネントが UI から削除される直前で実行される。
第2引数で副作用が依存する値の配列を渡して、副作用の実行条件を指定できる。

第2引数 動作
省略 毎レンダリング時に実行される
空配列 初回レンダリング時のみ実行される
依存している値の配列 初回レンダリングおよび渡した値が変化した場合に実行される
useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);
タピオカタピオカ

useContext

コンテクストオブジェクトを受け取り、そのコンテクストの現在値を返す。

const value = useContext(MyContext);

コンテクストの値が変化するたびに再レンダリングされる。
コンテクストの用途としては以下のようなものがある。

  • 認証済みユーザ
  • テーマ
  • 優先言語
タピオカタピオカ

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

useState の親戚。
useState よりも複雑なロジックが絡んだ状態を宣言するのに適している。

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

useReducer を用いることで、ステート更新関数をステート非依存にすることを強制できます。
あるステートを更新するときに別のステートを見る必要が発生するかもしれません。そのときが useReducer 導入のサインです。ぜひリファクタリングして useReducer を導入しましょう。
useReducerの本質:良いパフォーマンスのためのロジックとコンポーネント設計 - Qiita

タピオカタピオカ

useCallback

メモ化されたコールバックを返す。

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

第2引数に依存する値の配列を渡す。
関数は依存配列の要素のいずれかが変化した場合のみ変化する。

タピオカタピオカ

useMemo

メモ化された値を返す。

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

レンダリング毎に高価な計算が実行されるのを避けることができる。

タピオカタピオカ

useRef

ref オブジェクトを返す。
概念としてはクラスのインスタンス変数のようなもの。

const refContainer = useRef(initialValue);

よくあるユースケースは DOM にアクセスするパターン。

const TextInputWithFocusButton = () => {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.focus()
  }

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

イベントハンドラや副作用内で直接 current の値を書きかることができ、クラスのインスタンス変数のように使うことができる。

const Timer = () => {
  const intervalRef = useRef()
  
  useEffect(() => {
    const id = setInterval(() => { /* ... */ });
    intervalRef.current = id;
    return () => {
      clearInterval(intervalRef.id);
    };
  });
}

current プロパティを書き換えても再レンダリングは発生しない。

タピオカタピオカ

useImperativeHandle

ref Props を提供するコンポーネントで、親コンポーネントに渡されるインスタンス値をカスタマイズするのに使用する。

useImperativeHandle(ref, createHandle, [deps]);

以下の例だと、親コンポーネントで <FancyInput ref={inputRef} /> をレンダーすると inputRef.current.focus() を呼べる。

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
タピオカタピオカ

useLayoutEffect

useEffect の親戚。
useEffect がレンダリング完了後に非同期的に実行されるのに対して、useLayoutEffect はレンダリング中に同期的に実行される。

視覚的に影響を与える副作用を useEffect で行うとちらつきが発生するので、それを回避する際に使える。

タピオカタピオカ

useDebugValue

React DevTools でカスタムフックのラベルを表示できる。

useDebugValue(value);

第2引数でフォーマット用関数を指定できる。

useDebugValue(date, date => date.toDateString());
タピオカタピオカ

useDeferredValue

特定の状態の更新の優先度が低いことを React に通知するために使える。

const deferredValue = useDeferredValue(value);

以下の例では、優先度の高い SearchInput の再レンダリングが、優先度の低い SearchSuggestions の再レンダリングによって阻害されないように、deferredQuery の更新を遅延している。

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

  const suggestions = useMemo(() =>
    <SearchSuggestions query={deferredQuery} />,
    [deferredQuery]
  );

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

useTransition

useDeferredValue の親戚。
特定の状態の更新の優先度が低いことを React に通知するために使える。

const [isPending, startTransition] = useTransition();

startTransition で状態の更新をラップすることで、その状態の更新はいい感じに遅延される。
更新待ちの状態は isPending という Boolean 変数で参照できる。

function App() {
  const [isPending, startTransition] = useTransition();
  const [count, setCount] = useState(0);
  
  function handleClick() {
    startTransition(() => {
      setCount(c => c + 1);
    })
  }

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

useDeferredValue が状態の値をラップするのに対し、useTransition は状態の更新コードをラップするという違いがある。
違いについては useTransition() vs useDeferredValue | React 18 - YouTube が分かりやすい。

タピオカタピオカ

useId

ハイドレーション時の不整合を防ぎつつサーバとクライアント間で安定な一意 ID を作成する。

const id = useId();

id を必要としている要素に直接渡すのが一般的な使い方。

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

複数の ID を使う場合は同じ id に sufix をつけて使う。

const 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>
  );
}
このスクラップは2022/07/18にクローズされました