React hooksを使ったチェックボックスを関数オブジェクトの再生成を抑えて実装する方法
追記
2020/11/10: formに渡すべき属性が一部不適切だったので修正しています。イベントハンドラ等には変更ありません。
はじめに
React hooksを使ったcheckboxの実装を調べてみても、関数オブジェクトの再生成を防ぐ方法が出てきませんでした。
自分でちゃんと実装したので、備忘がてら紹介します。
要件
React hooksを用いたFunctionalComponentで、チェックボックスを実装する。
その際に、イベントハンドラの関数オブジェクトが再生成されないような実装を行う。
また、uncontrolled componentは推奨されていないので使わない(controlled componentを使う)。
調べるとよく出てくる実装(関数オブジェクトが再生成されてしまう)
const Checkbox = () => { const [checked, setChecked] = useState(false); const handleCheckboxClick = useMemo(() => { console.log("function generated in Checkbox"); return (e) => { setChecked(!checked); }; }, [checked]); return ( <label htmlFor="checkbox"> {checked ? "clicked!!" : "click me"} <input checked={checked} name="checkbox" onChange={handleCheckboxClick} type="checkbox" id="checkbox" value="somevalue" /> </label> ); };
こんな感じのコードが紹介されている場合が多いようです。
この実装ではたとえuseCallbackを使ったとしても、depsにcheckedが入ってしまうので、チェックされる度に関数オブジェクトが再生成されることを避けられません。
僕の考えた実装例
先程の実装例のうち、handleCheckboxClick
の定義(と関数名)のみを書き換えています。
const MyCheckbox = () => { const [checked, setChecked] = useState(false); const handleCheckboxClick = useMemo(() => { console.log("function generated in MyCheckbox"); return (e) => { setChecked(e.target.checked); }; }, []); return ( <label htmlFor="mycheckbox"> {checked ? "clicked!!" : "click me"} <input checked={checked} name="mycheckbox" onChange={handleCheckboxClick} type="checkbox" id="mycheckbox" value="somevalue" /> </label> ); };
それほど複雑ではないですが、これで関数オブジェクトが再生成されることはありません。
また、このようにイベントから変化後の値を取るようにすると、関数自体は(副作用はあるけれど)stateの値には依存しなくなるため、設計としても改善していると思います。
検証
検証用のCodeSandboxのコードを置いておきます。
consoleタブを開いている状態で両者のコードを実行すると、MyCheckboxのほうは関数の再生成が起こっていないことが見てとれます。
なお、このコードでは関数が生成されたことを検出するためにuseCallbackではなくuseMemoを使っています。
おわりに
この程度でも適切な実装例が全然出てこないので、ちょっと不安になっています。
自分の実装が間違っている可能性もあるので、気づいた方はtwitter等までご連絡いただければ幸いです。