📌

React Slotが便利✨

2022/06/19に公開

Radix UIのSlotコンポーネントが地味ですが、大変便利です。

https://www.radix-ui.com/docs/primitives/utilities/slot

概要

Radix UIにはユーティリティのコンポーネントであるSlotがあります。

実装は実にシンプルです。

https://github.com/radix-ui/primitives/blob/main/packages/react/slot/src/Slot.tsx

提供する機能もシンプルですが非常に強力です。

基本

直下の子コンポーネントに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関数がこの役割を担います。

https://github.com/radix-ui/primitives/blob/main/packages/react/slot/src/Slot.tsx#L91-L116

  • 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とマージする

これがSlotSlottableの動作です。

何がうれしいのか

~別記事に譲りますが執筆中です。~

そのままですが、子コンポーネントXに任意のコンポーネントをXの子供として後から押し込みたい場合に使用できます(説明がわかりづらいので先ほどのコードで雰囲気をつかんでいただきたいです)。

Framer Motionの解説がなされているこちらの記事の「Shared layout animations and LayoutGroup」に登場するリッチなホバーアニメーションがあります。
これを極力再利用できるような形に落とし込みたかったので、SlotSlottableを利用しました。

https://github.com/sub-t/framer-lab/blob/main/src/animate/HoverHighlight/HoverHighlightItem.tsx

Framer MotionとSlotの併用なので、当初はこちらの記事で紹介するつもりでした。ただ、明らかに混乱を招くことが予想されたので、行き場所を見失っていました。結局、別記事に譲ることなくこちらで供養することにしました。

まとめ

Slot単体で非常に便利でしたが、Slottable込みの場合のSlotの動作が変更された(される?)ことで、より強力な機能を提供してくれるようになりました。

Slotの実装はシンプルなので、mergePropsの挙動やSlottable込みの挙動が気に入らない場合は、オリジナルのSlotを実装してもいいかもしれませんね。

Discussion