はじめに
Reactで親コンポーネントの動的な値(幅や高さなど)を、深くネストした子コンポーネントに伝えたいことがあります。
propsで渡すのが基本ですが、CSS変数をstyle属性に定義することで、propsを使わずに子孫要素から値を参照できます。
基本的な使い方
親コンポーネントでCSS変数を定義
import { CSSProperties } from 'react';
const Parent = () => {
const [width, setWidth] = useState(400);
return (
<div
style={{
'--container-width': `${width}px`,
width: 'var(--container-width)',
} as CSSProperties}
>
<Child />
</div>
);
};
ポイント:
- CSS変数は
--で始まる名前をつける - TypeScriptでは
as CSSPropertiesでキャストが必要
子コンポーネントから参照
// propsで幅を受け取る必要がない
const Child = () => {
return (
// 親で定義したCSS変数を参照
<div className="w-[calc(var(--container-width)-24px)]">
<p className="truncate">長いテキスト...</p>
</div>
);
};
CSSのvar()関数でCSS変数を参照できます。Tailwind CSSのarbitrary valuesでも使えます。
何が嬉しいのか
propsのバケツリレーが不要
通常、深くネストした子コンポーネントに値を渡すには、中間のコンポーネントすべてにpropsを経由させる必要があります。
<Parent width={width}>
<Section width={width}>
<Container width={width}>
<Card width={width} /> {/* やっと使う */}
</Container>
</Section>
</Parent>
CSS変数なら、親で定義するだけで子孫のどこからでも直接参照できます。
<Parent style={{ '--width': `${width}px` }}>
<Section>
<Container>
<Card /> {/* var(--width) で参照 */}
</Container>
</Section>
</Parent>
動的な値でも機能する
stateで管理している値をCSS変数に入れれば、値が変わるたびにCSS変数も更新されます。リサイズ可能なパネルの幅など、頻繁に変わる値にも対応できます。
子コンポーネントの再レンダーを抑制できる
propsで幅を渡すと、値が変わるたびに子コンポーネントも再レンダーされます。
CSS変数を使えば、子コンポーネントのpropsは変わらないため、React.memoと組み合わせることで子の再レンダーをスキップできます。
// memo化した子コンポーネント
const Card = memo(() => {
return (
<div className="w-[var(--width)]">...</div>
);
});
レイアウトの更新はブラウザのCSS再計算で行われ、Reactの再レンダーは親コンポーネントだけで済みます。
デメリット
依存関係が暗黙的になる
propsなら「このコンポーネントはwidthを必要とする」と型で明示されますが、CSS変数は暗黙の依存です。
// props: 依存が明確
const Card = ({ width }: { width: number }) => { ... }
// CSS変数: 親に --width が定義されている前提(型で表現できない)
const Card = () => {
return <div className="w-[var(--width)]">...</div>
}
どのCSS変数に依存しているかはコードを読まないとわかりません。
デバッグしづらい
型安全ではない
CSS変数名はただの文字列なので、typoしても型エラーになりません。
まとめ
使い所はよく考えよう!