Slot 和 Fill 是一对组件,使开发者能够在 React 元素树的其他位置渲染 React UI,这种模式通常被称为“portal”渲染。这是一种组件扩展性模式,单个 Slot 可以被应用不同部分中渲染的多个 Fill 所占据。

Slot/Fill 最初受到 react-slot-fill 库 的启发。

用法

在应用的根节点处,必须渲染一个 SlotFillProvider,以协调 Slot 和 Fill 的渲染。

然后,在应用的任何位置渲染一个 Slot 组件,并为其指定一个 name。name 可以是 string 或 symbol。Symbol 名称对于旨在私有的插槽非常有用,仅允许拥有该 symbol 值的客户端访问。

任何 Fill 都会在此 Slot 空间中渲染其 UI,即使它是在应用的其他位置渲染的。

您可以直接使用 Fill 组件,或者创建一个包装组件(如下例所示),以向使用者隐藏插槽名称。

import {
    SlotFillProvider,
    Slot,
    Fill,
    Panel,
    PanelBody,
} from '@wordpress/components';

const MySlotFillProvider = () => {
    const MyPanelSlot = () => (
        <Panel header="Panel with slot">
            <PanelBody>
                <Slot name="MyPanelSlot" />
            </PanelBody>
        </Panel>
    );

    MyPanelSlot.Content = () => <Fill name="MyPanelSlot">Panel body</Fill>;

    return (
        <SlotFillProvider>
            <MyPanelSlot />
            <MyPanelSlot.Content />
        </SlotFillProvider>
    );
};

还有一个 createSlotFill 辅助方法,它是为了简化匹配相应的 Slot 和 Fill 组件的过程而创建的:

const { Fill, Slot } = createSlotFill( 'Toolbar' );

const ToolbarItem = () => <Fill>My item</Fill>;

const Toolbar = () => (
    <div className="toolbar">
        <Slot />
    </div>
);

Props

SlotFillProvider 组件不接受任何 props(children 除外)。

Slot 和 Fill 都接受一个 name 字符串 prop,具有给定 name 的 Slot 将渲染任何关联的 Fill 的 children。

Slot 接受一个 bubblesVirtually prop,它改变了渲染 Fill children 的方法。使用 bubblesVirtually 时,Fill 是使用 React portal 渲染的。这会影响事件冒泡和 React context 传播行为:

bubblesVirtually=false

  • 事件将冒泡到 DOM 层次结构中的父级(原生事件冒泡)
  • Fill 内的 React 元素将以 Slot 的 React context 进行渲染
  • 直接在 Fragment 内渲染 Fill 元素,没有包装 DOM 元素

bubblesVirtually=true

  • 事件将冒泡到 React 元素层次结构中的虚拟(React)父级
  • Fill 内的 React 元素将保留 Fill 及其父级的 React context
  • 渲染一个包装 DOM 元素,在其中渲染 Fill 元素(用作 React createPortal 的参数)

bubblesVirtually=true 的 Slot 会渲染一个包装 DOM 元素(默认为 div),并接受自定义此元素的额外 props,如 className 或 style。您还可以通过传递 as prop 将 div 替换为其他元素。

不带 bubblesVirtually 的 Slot 接受一个可选的 children prop,它是一个接收 fills 数组作为参数的函数。它允许您执行额外的处理:当没有 fills 时渲染占位符,或者仅在存在 fills 时渲染包装器。

示例:

const Toolbar = ( { isMobile } ) => (
    <div className="toolbar">
        <Slot name="Toolbar">
            { ( fills ) => {
                return isMobile && fills.length > 3 ? (
                    <div className="toolbar__mobile-long">{ fills }</div>
                ) : (
                    fills
                );
            } }
        </Slot>
    </div>
);

附加信息(props)也可以通过以下组合从 Slot 传递给 Fill:
1. 向 Slot 添加 fillProps prop。
2. 将函数作为 children 传递给 Fill。该函数将接收 fillProps 作为参数。

const { Fill, Slot } = createSlotFill( 'Toolbar' );

const ToolbarItem = () => (
    <Fill>
        { ( { hideToolbar } ) => {
            <Button onClick={ hideToolbar }>Hide</Button>;
        } }
    </Fill>
);

const Toolbar = () => {
    const hideToolbar = () => {
        console.log( 'Hide toolbar' );
    };
    return (
        <div className="toolbar">
            <Slot fillProps={ { hideToolbar } } />
        </div>
    );
};