React RFCs(request for comments)に登場した新しいhooksのuseEventの長所と使い所をまとめてみましたので、参考にして頂ければ幸いです。
RFCについて
Reactのコアメンバーとコミュニティメンバーが共同で新機能を設計できることを目的としており、誰でも参加可能でCLS(貢献者ライセンス契約)受理後にマークダウンファイルPRが提出できるようになります。
PRがマージされるとRFCは「アクティブ」となり、最終的にReactに取り込むことを目標に実装することができます。
詳しくはこちらのREADMEを参照
hooksにおける課題(コールバック関数編)
親コンポーネントから子コンポーネントにpropsでコールバック関数を渡すシーンにおいて以下のようなコードがあります。
const Chat = () => {
const [text, setText] = useState('')
const onClick = () => {
sendMessage(text)
}
return <SendButton onClick={onClick} />
}
このコードの問題点はテキストが変更されるたびに、onClick
関数が再生成されてしまうことで、SendButton
が毎回再レンダリングされてしまいます。
useCallbackを使って解決しようとしたパターン
const Chat = () => {
const [text, setText] = useState('')
const onClick = useCallback(() => {
sendMessage(text)
}, [text])
return <SendButton onClick={onClick} />
}
関数が再生成されない限り再レンダリングが走らないようになりましたが、テキストが変わるたびにonClick
関数が再生成されて、SendButton
が毎回再レンダリングされます。
既存のhooksを使った解決策
const Chat = () => {
const [text] = useState('')
const sendRef = useRef(null)
useLayoutEffect(() => {
sendRef.current = () => sendMessage(text)
})
const onClick = useCallback((...args) => {
return sendRef.current(...args)
}, [])
return <SendButton onClick={onClick} />
}
かなり可読性が悪いですよね^^;
useEventで解決するパターン
const Chat = () => {
const [text, setText] = useState('')
const onClick = useEvent(() => {
sendMessage(text)
})
return <SendButton onClick={onClick} />
}
useEvent()
を使えば、関数内で使われるプロップやステートが変わっても、同じ参照整合性を維持することができます。
hooksにおける課題(useEffect編)
次に、ページが更新した際にURLとユーザー名のログを残す実装でuseEffect
を使った例です。
const Page = () => {
useEffect(() => {
logAnalytics('visit_page', route.url, currentUser.name)
}, [route.url, currentUser.name])
}
こちらの問題点は、本来であればURLが変わった時のみlogAnalytics()
を実行したいのに、ユーザー名が変わったときにも走ってしまいます。currentUser.name
を消しても、ESLintのuseEffect has amissing dependency
の警告が出てしまいます^^;
useCallbackを使って解決しようとしたパターン
const Page = () => {
const logVisit = useCallback((url) => {
logAnalytics('visit_page', url, currentUser.name)
}, [currentUser.name])
useEffect(() => {
logVisit(route.url)
}, [route.url])
}
これでも、currentUser.name
が変わるたびに関数が再生成される問題は残っています。
useEventで解決するパターン
const Page = () => {
const logVisit = useEvent((pageUrl) => {
logAnalytics('visit_page', pageUrl, currentUser.name)
})
useEffect(() => {
logVisit(route.url)
}, [route.url])
}
これで、useEffect
関数本体からcurrentUser.name
を削除し、route.url
が変更されたときのみlogVisit()
が再生成されずに実行することができるようになります。依存配列もなくなりスッキリしますね!
考察
useEventは現状のhooksにおける大きな課題になっている、コールバック関数が再生成されることによる再レンダリングとuseEffectやuseCallback内での無駄な実行を防止し、依存配列の指定に早く公式採用されるのを心待ちにしております。