useInsertionEffect

落とし穴

useInsertionEffect は CSS-in-JS ライブラリの作者向けです。CSS-in-JS ライブラリの開発をしておりスタイルを挿入する場所を必要としているのでない限り、おそらく useEffect または useLayoutEffect の方が適切です。

useInsertionEffect はレイアウトエフェクトが発火する前に DOM に要素を挿入するために使用します。

useInsertionEffect(setup, dependencies?)

リファレンス

useInsertionEffect(setup, dependencies?)

useInsertionEffect を呼び出して、レイアウトを読み取る可能性があるエフェクトが実行される前にスタイルの挿入を行います。

import { useInsertionEffect } from 'react';

// Inside your CSS-in-JS library
function useCSS(rule) {
useInsertionEffect(() => {
// ... inject <style> tags here ...
});
return rule;
}

さらに例を見る

引数

  • setup: エフェクトのロジックが記述された関数です。このセットアップ関数は、オプションでクリーンアップ関数を返すことができます。コンポーネントが初めて DOM に追加された後、レイアウトエフェクトが発火する前に、React はセットアップ関数を実行します。依存配列 (dependencies) が変更された再レンダー時には、React はまず古い値を使ってクリーンアップ関数(あれば)を実行し、次に新しい値を使ってセットアップ関数を実行します。コンポーネントが DOM から削除された後、React はクリーンアップ関数を最後にもう一度実行します。

  • 省略可能 dependencies: setup コード内で参照されるすべてのリアクティブな値のリストです。リアクティブな値には、props、state、コンポーネント本体に直接宣言されたすべての変数および関数が含まれます。リンタが React 用に設定されている場合、すべてのリアクティブな値が依存値として正しく指定されているか確認できます。依存値のリストは要素数が一定である必要があり、[dep1, dep2, dep3] のようにインラインで記述する必要があります。React は、Object.is を使った比較で、それぞれの依存値を以前の値と比較します。この引数を省略すると、エフェクトはコンポーネントの毎回のレンダー後に再実行されます。

返り値

useInsertionEffectundefined を返します。

注意点

  • エフェクトはクライアント上でのみ実行されます。サーバレンダリング中には実行されません。
  • useInsertionEffect の内部から state を更新することはできません。
  • useInsertionEffect が実行される時点では、まだ ref はアタッチされていません。
  • useInsertionEffect は DOM の更新前に実行される場合も後に実行される場合もあります。タイミングに関わらず、DOM が更新されていることを前提としてはいけません。
  • 他の種類のエフェクトはすべてのエフェクトにクリーンアップを実行してからすべてのエフェクトにセットアップを行います。一方で useInsertionEffect はコンポーネントごとにクリーンアップとセットアップをまとめて行います。結果的にクリーンアップとセットアップが「交互に実行される」ような挙動になります。

使用法

CSS-in-JS ライブラリからの動的スタイル注入

従来、React コンポーネントのスタイル付けはプレーンな CSS を使用して行われていました。

// In your JS file:
<button className="success" />

// In your CSS file:
.success { color: green; }

チームによっては、CSS ファイルを書く代わりに JavaScript コード内で直接スタイルを記述することを好む場合があります。これには通常、CSS-in-JS ライブラリやツールが必要です。CSS-in-JS には以下の 3 つの一般的なアプローチがあります。

  1. コンパイラを使用した CSS ファイルへの静的な抽出
  2. インラインスタイル、例えば <div style={{ opacity: 1 }}>
  3. <style> タグのランタイム時の注入

CSS-in-JS を使用する場合、最初の 2 つのアプローチの組み合わせ(静的スタイルには CSS ファイル、動的スタイルにはインラインスタイル)を推奨します。ランタイム時の <style> タグの注入は、以下の 2 つの理由から推奨しません:

  1. ランタイム時の注入は、ブラウザにスタイルの再計算を頻繁に強制します。
  2. ランタイム時の注入は、React ライフサイクル中の間違ったタイミングで行われると非常に遅くなることがあります。

このうち最初の問題は解決不可能ですが、useInsertionEffect は 2 つ目の問題を解決するのに役立ちます。

レイアウトエフェクトの発火前にスタイルを挿入するために useInsertionEffect を呼び出しましょう。

// Inside your CSS-in-JS library
let isInserted = new Set();
function useCSS(rule) {
useInsertionEffect(() => {
// As explained earlier, we don't recommend runtime injection of <style> tags.
// But if you have to do it, then it's important to do in useInsertionEffect.
if (!isInserted.has(rule)) {
isInserted.add(rule);
document.head.appendChild(getStyleForRule(rule));
}
});
return rule;
}

function Button() {
const className = useCSS('...');
return <div className={className} />;
}

useEffect と同様に、useInsertionEffect はサーバ上では実行されません。サーバ上でどの CSS ルールが使用されたかを収集する必要がある場合、レンダー中に行うことができます。

let collectedRulesSet = new Set();

function useCSS(rule) {
if (typeof window === 'undefined') {
collectedRulesSet.add(rule);
}
useInsertionEffect(() => {
// ...
});
return rule;
}

useInsertionEffect でランタイム時にスタイルを注入するよう CSS-in-JS ライブラリをアップグレードする場合の詳細

さらに深く知る

この手法がレンダー中や useLayoutEffect でスタイルを注入するより優れている理由

レンダー中、React が非ブロッキング更新を処理している最中にスタイルを挿入すると、ブラウザはコンポーネントツリーのレンダー中に毎フレームスタイルを再計算することになり、これは非常に遅くなることがあります。

useInsertionEffect は、useLayoutEffectuseEffect でスタイルを挿入するよりも優れています。なぜなら、他のエフェクトがあなたのコンポーネントで実行される時点で <style> タグがすでに挿入されていることが保証されるからです。さもないと、古くなったスタイルにより、通常のエフェクトでのレイアウト計算が正しくなくなってしまいます。