👋

【React】setStateの引数に関数を渡すと何が嬉しいのか

2021/08/26に公開

はじめに

ReactのuseStateでstateを更新する際、引数には

  • 新しいstate
  • 以前のstateを引数にとり、新しいstateを返す関数

のいずれかを渡すことになります。

const [state, setState] = useState({id:1,value:''});

const setValue = (value:string) => setState({...state, value});
const [state, setState] = useState({id:1,value:''});

const setValue = (value:string) => setState((prev) => ({
    ...state, value
}));

どちらのパターンでも動作はしますが、後者の以前のstateを引数にとり、新しいstateを返す関数を渡すパターンには、前者と違って「stateに依存しない」というメリットが存在します。

なぜstateへの依存を無くしたいのか

state更新用の関数がstateそのものに依存してしまうと、useCallbackによるメモ化が上手く働きません。下記がサンプルコードとなります。

const [state, setState] = useState({id:1,value:''});

const setValue = useCallback((value:string) => setState({...state, value}), [state]);

上記のsetValue関数は関数外部のstateに依存しているため、useCallbackによるメモ化を行う際には、依存配列にstateを含める必要があります。
それにより、stateが更新されるタイミングで関数も再生成されてしまいます。

それに対して、以前のstateを引数にとり、新しいstateを返す関数を渡すパターンでuse
Callbackを用いる場合、下記のようなコードになります。

const [state, setState] = useState({id:1,value:''});

const setValue = useCallback((value:string) => setState((prev) => ({
    ...prev, value
})),[]);

依存配列からstateを除去できるため、関数のメモ化を適切に行うことが可能です。

まとめ

useCallbackによるメモ化を行わないのであれば、どちらのパターンでも正しく動作します。
しかし、再レンダリング抑制のためにチューニングが必要になった場合が問題です。
更新用の関数がstateに依存することで思わぬ挙動に繋がりますので、「メモ化された値 / 関数」が更新されるタイミングには気を配っておきましょう。

Discussion