React18が2021年11月にベータ版となりましたが、Reactの経過を知るため、React17の変更を今一度読み返します。
↓公式リリースノートから抜粋しています
https://ja.reactjs.org/blog/2020/08/10/react-v17-rc.html
React17での変更
1.段階的なアップグレードが可能
React18などのバージョンをアップグレードする際にアプリ全体をアップグレードせずに段階的にアップグレードを進めることができる。
例えば、アプリの大部分を React 18 に移行しつつ、いくつかの遅延ロードされるダイアログやサブページを React 17 のままにしておける。
段階的に更新しないといけないという訳ではなく、一気にアップグレードするのがベストの選択肢ではある。
2.イベントデリゲーションに関する変更
Reactはdocumentレベルにイベントハンドラをアタッチしなくなり、DOMツリーのrootDOMコンテナにアタッチするようになる。
Reactはイベントに対してdocument.addEventListener() のようにしていたが、React17は、rootNode.addEventListener() という呼び出しを行う。
気を付けること
React内でdocument.addEventListener(...)とう形でイベントリスナを登録しても、dcumentレベルには到達しない(rootNodeまで)ため、e.stopPropagation()を使ってReact内の全てのイベントの制御はできない。
dcumentレベルでのイベントを実行するには、document.addEventListenerの第3引数として { capture: true }を追加し、イベントリスナがキャプチャフェーズで呼ばれるようにコードを変更する
キャプチャフェーズとは?
キャプチャリングとバブリング
DOMイベントは3つのフェーズがある。
- キャプチャフェーズ(イベントが下る)
- ターゲットフェーズ(ターゲットに到達)
- バブリングフェーズ (ターゲット要素から上にバブル)
要素をクリックした場合の流れは、
まずキャプチャフェーズでイベントがWindowから要素に向かって降りていく。
ターゲットフェーズでターゲット要素に到達し、
その後、バブリングフェーズでハンドラを呼び出しながら上に伝達して行く(バブル)。
キャプチャフェーズは通常は使われない。
プロパティまたはHTML属性、もしくはaddEventListener(event, handler) を使って追加されたハンドラはキャプチャリングはせず、2と3のみで行われる。
キャプチャリングフェーズでイベントをキャッチするには、addEventListenerの3つ目の引数を true にする必要がある。
3.ブラウザとの整合性向上
- onScrollイベントはバブリングしないようになった。
- ReactのonFocusとonBlurイベントはネイティブのfocusinとfocusoutイベントを裏で使うように変更(以前はfocus)
- onClickCaptureといったキャプチャフレーズイベントは、実際のブラウザのキャプチャフレーズのリスナーとなった
4.イベントプーリングの廃止
イベントハンドラが実行された後にオブジェクトのプロパティにアクセスする必要がある場合は、e.persist() を呼ぶ必要があったが、このような最適化は必要なくなった。
function handleChange(e) {
// Prevents React from resetting its properties:
e.persist(); // ← React17からは必要ない
setTimeout(() => {
console.log(e.target.value); // Works
}, 100);
}
5.副作用クリーンアップのタイミング
クリーンアップのタイミングも非同期になった。また、実行するタイミングは新しい副作用より前。
useEffect内の関数は画面に更新が反映された直後に非同期的に実行しするが、クリーンアップ関数は同期的に実行されていた。大きな画面遷移(タブの切り替えなど)がある場合に遅くなってしまう問題があった。
気を付けること
useEffect(() => {
someRef.current.someSetupMethod(); // <=クリーンアップ実行時にはnullになっている場合がある
return () => {
someRef.current.someCleanupMethod();
};
});
useEffect(() => {
const instance = someRef.current; // <=副作用の内部で書き換わる可能性のある値をキャプチャ
instance.someSetupMethod();
return () => {
instance.someCleanupMethod();
};
});
6. Undefined を返した場合の一貫性のあるエラー
forwardRefとmemoコンポーネントではこのような返り値のチェックを行っていなかったが、undefinedを返すことはエラーになる。
通常の関数・クラスコンポーネントの振る舞いと合致するように変更。
参考