React Slotが便利✨
Radix UIのSlotコンポーネントが地味ですが、大変便利です。
概要
Radix UIにはユーティリティのコンポーネントであるSlotがあります。
実装は実にシンプルです。
提供する機能もシンプルですが非常に強力です。
基本
直下の子コンポーネントにpropsを渡すことができます。
子コンポーネントにpropsを渡す方法としてはcloneElement
が有名(検索がよくヒットする)ですが、ほとんどのケースにおいてSlot
に置き換えたほうが実装が楽になります。
実装がシンプルになる
単純に記述量が減ります。
これが
const Component: FC<Props> = ({ children }) => {
const newChildren = cloneElement(children as ReactElement, { color: 'red' });
return <div>{newChildren}</div>;
};
こうなります。
const Component: FC<Props> = ({ children }) => {
return (
<div>
<Slot color="red">{children}</Slot>
</div>
);
};
ReactNode
型で定義することの多いchildren
をキャストする手間も省けるので楽です。
mergeProps
がありがたい
内部的に、子コンポーネントに元々あるpropsとSlot
から渡されるpropsの衝突を抑止してくれます。
mergeProps
関数がこの役割を担います。
-
onXXXX
ハンドラは子コンポーネントのハンドラから実行 - スタイリング要素は文字列として連結(Slot優先)
するように手を加えていることがわかります。
className
のマージが発生するような箇所なんかは特に楽です。何も考えずに対象コンポ―ネントをSlot
で囲って、そこにclassName
を含むpropsを流し込むだけなので。
Slottable
Slottable
を併用したときのSlot
の挙動は少々特殊です。
例えば、次のようにComponent
を実装したとき、
const Component = () => (
<Slot {...slotProps}>
<Child1 />
<Slottable>
<Parent>
<Child2 />
</Parent>
</Slottable>
<Child3 />
</Slot>
);
Component
は、こうなります。
const Component = () => (
<Parent {...slotProps}>
<Child1 />
<Child2 />
<Child3 />
</Parent>
);
-
Slottable
直下の子コンポーネント(Parent
)をSlot
の位置に巻き上げる - さらにその子コンポーネント(
Child2
)をSlot
配下のchildren
とマージする
これがSlot
・Slottable
の動作です。
何がうれしいのか
~別記事に譲りますが執筆中です。~
そのままですが、子コンポーネントXに任意のコンポーネントをXの子供として後から押し込みたい場合に使用できます(説明がわかりづらいので先ほどのコードで雰囲気をつかんでいただきたいです)。
Framer Motionの解説がなされているこちらの記事の「Shared layout animations and LayoutGroup」に登場するリッチなホバーアニメーションがあります。
これを極力再利用できるような形に落とし込みたかったので、Slot
・Slottable
を利用しました。
Framer MotionとSlotの併用なので、当初はこちらの記事で紹介するつもりでした。ただ、明らかに混乱を招くことが予想されたので、行き場所を見失っていました。結局、別記事に譲ることなくこちらで供養することにしました。
まとめ
Slot
単体で非常に便利でしたが、Slottable
込みの場合のSlot
の動作が変更された(される?)ことで、より強力な機能を提供してくれるようになりました。
Slot
の実装はシンプルなので、mergeProps
の挙動やSlottable
込みの挙動が気に入らない場合は、オリジナルのSlot
を実装してもいいかもしれませんね。
Discussion