useEvent
RFC
https://github.com/reactjs/rfcs/pull/220
https://github.com/facebook/react/pull/25229
#WIP
2022/5にReact teamによってRFCが出された新しいhooks
useStateなどに並ぶ、Reactに組み込みのhooksになる
polyfillでは不十分なので、組み込みで入れる ref
割と大きめの変更ではあるが、Reactのminor versionで追加する予定 ref
雑に言うと、明示的にdepsを書かなくても良いuseCallbackみたいなもの
event handlerに対して使用することを目的としているので、一旦useEventという名前になっている
discussionでは名前についての議論も多い
あと、頻繁に使われることになるので短い命名のほうがいい
親(Chat)の中で作った関数を、子(SendButton)のpropsとして渡すやつ
code:ts
function Chat() {
const text, setText = useState('');
// 🟡 Always a different function
const onClick = () => {
sendMessage(text);
};
return <SendButton onClick={onClick} />;
}
Reactを書いていると、めちゃくちゃ遭遇するパターン
この時、onClickはrenderingごとに毎回生成される
その対策として、従来は、useCallbackとReact.memoを組み合わせて使っていた
useCallbackのdepsに入るobjectが頻繁に変わる場合は、依然として問題となる
useEventはこの問題を解決する
関数内で使用するobjectに変更があっても、関数の再生成を行わない
code:ts
function Chat() {
const text, setText = useState('');
// 🟡 Always a different function
const onClick = useEvent(() => {
sendMessage(text);
});
return <SendButton onClick={onClick} />;
}
他の例
useEffect内でsocketを管理している例 ref
useEffect内でsocketに接続している
depsにthemeがある
themeが変わるたびに、socketが再接続されてしまう
確かに、これuseEventなしでどうやって解決するんだmrsekut.icon
受け取ったものが、普通の関数でも問題ない
このコード例
code:ts
function Chat({ selectedRoom }) {
const onConnected = (connectedRoom) => {..};
const onMessage = (message) => {..};
useRoom(selectedRoom, { onConnected, onMessage });
// ...
}
function useRoom(room, events) {
const onConnected = useEvent(events.onConnected); // ✅ Stable identity
const onMessage = useEvent(events.onMessage); // ✅ Stable identity
// ..
}
useRoomでは、引数のeventsで2つの関数を受け取っている
これらの関数はuseCallbackなどで、stableになっていない
でも、useRoom内でuseEventしてから使えば問題ない
もちろんこれら2つの関数がChat内でuseEventを使っていてもいい
2重のuseEventになるが、問題ない
これはカプセル化が強化されたと言えるmrsekut.icon
こんな感じの内部実装になる ref
実際は、組み込みなのでもっと効率の良いものになるはず
code:ts
function useEvent(handler) {
const handlerRef = useRef(null);
// In a real implementation, this would run before layout effects
useLayoutEffect(() => {
handlerRef.current = handler;
});
return useCallback((...args) => {
// In a real implementation, this would throw if called during render
const fn = handlerRef.current;
return fn(...args);
}, []);
}
useEventにwrapされたevent handlerは、rendering中に呼び出されるとthrowする
SSRの場合、useEventは全ての呼び出しに対して同じthrowing shimを返す
なにこれ #??
usesEventを使うべきでない時 ref
rendering中に呼ばれる関数
useCallbackを使うべき
useEventを使うとthrowされる
Not all functions in effect dependencies are events
https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md#not-all-functions-in-effect-dependencies-are-events
よくわからん
変化したときに、再renderingして欲しい時
https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md#not-all-functions-extracted-from-effects-are-events
useEffect内で呼ぶ関数をまるごとuseEventに入れちゃうと理想通りの挙動にならない
それはそうmrsekut.icon
現状考えられる欠点と、ソレについてのコメント ref
読んだ感じそんなヤバい欠点でもなさそうmrsekut.icon
#??
どうやって実現している?
useCallbackの実装時にはコレを思いついていなかった?
これだけの説明だったら明らかにuseCallbackよりよいと思うけど何か問題あるの?
useCallback不要になる?
そんなことはない
rendering中に呼ばれる関数などは、useCallbackを使う必要がある
useCallback remains useful for cases where a function is used while rendering. However, it'll probably be deemphasized with time as it won't be needed as often. ref
ただし、今後はあまり使われなくはなるだろう
ほぼ全ての関数が、useEventを使うことになるよね
逆に、素のままの関数を定義することってあるのか?
極論言えば、defaultでuseEventになるようにすればいい、みたいになる
ただ、全てそうしたら問題があるので、defaultになんてことはできない
Force React event handlers to always be declared with useEvent. This seems premature at this point. ref
時期尚早
https://blog.koba04.com/post/2022/05/14/speaking-about-useevent-at-techfeed-conf
https://speakerdeck.com/koba04/how-useevent-would-change-our-applications