React 实现插槽完整示例(匿名 / 具名 / 作用域插槽)

React 实现插槽完整示例(匿名 / 具名 / 作用域插槽)

先说明核心原理: Vue 的插槽本质是预留渲染位置 + 传递渲染内容 ,React 没有原生 slot 语法,靠两类方案实现:

  1. children 属性:实现匿名插槽
  2. props 传递 JSX 节点:实现具名插槽
  3. props 传递返回 JSX 的函数:实现作用域插槽(传子组件内部数据给父层)

下面统一封装一个 Card 卡片组件,完整覆盖三种插槽。

1. 匿名插槽(对应 Vue 默认 slot)

子组件 Card.jsx

复制代码
// 匿名插槽:this.props.children / props.children
export default function Card({ children }) {
  return (
    <div className="card" style={{ border: "1px solid #ccc", padding: 16 }}>
      <h3>卡片标题</h3>
      {/* 匿名插槽位置 */}
      <div className="card-body">
        {children}
      </div>
    </div>
  );
}

父组件使用

复制代码
import Card from "./Card";

export default function Page() {
  return (
    <div>
      <Card>
        {/* 传入匿名插槽内容 */}
        <p>这里是匿名插槽内容</p>
        <button>按钮</button>
      </Card>
    </div>
  );
}

2. 具名插槽(对应 Vue name="header/footer")

思路:通过 props 传递 JSX,约定 header / footer 等插槽名。

子组件 Card.jsx

复制代码
// header、footer 两个具名插槽,children 匿名插槽
export default function Card({ header, footer, children }) {
  return (
    <div className="card" style={{ border: "1px solid #ccc", padding: 16 }}>
      {/* 具名插槽 header */}
      {header && <div className="card-header">{header}</div>}

      {/* 匿名插槽主体 */}
      <div className="card-body">{children}</div>

      {/* 具名插槽 footer */}
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

父组件使用

复制代码
import Card from "./Card";

export default function Page() {
  return (
    <div>
      <Card
        {/* 传递具名插槽内容 */}
        header={<h2>自定义头部具名插槽</h2>}
        footer={<span>底部操作栏 · 具名插槽</span>}
      >
        {/* 匿名插槽 */}
        <p>中间主体内容</p>
      </Card>
    </div>
  );
}

3. 作用域插槽(核心:子组件传数据给父插槽内容)

Vue:<template #item="scope">{``{ scope.row }}</template> React 方案:props 接收渲染函数,子组件调用函数并传入内部数据。

子组件 List.jsx(列表,内部有每条 item 数据)

复制代码
// scopeSlot 是渲染函数,子组件调用时把内部 item 传给父层
export default function List({ data, scopeSlot }) {
  return (
    <ul>
      {data.map((item) => (
        <li key={item.id}>
          {/* 调用父传进来的渲染函数,把子组件内部 item 暴露出去 */}
          {scopeSlot(item)}
        </li>
      ))}
    </ul>
  );
}

父组件使用(接收子组件传递的 item 数据)

复制代码
import List from "./List";

export default function Page() {
  const listData = [
    { id: 1, name: "苹果", price: 12 },
    { id: 2, name: "香蕉", price: 5 },
  ];

  return (
    <div>
      <List
        data={listData}
        {/* 作用域插槽:函数参数就是子组件暴露的数据 */}
        scopeSlot={(item) => (
          <>
            <span>{item.name}</span>
            <span style={{ color: "red" }}>¥{item.price}</span>
          </>
        )}
      />
    </div>
  );
}

4. 混合案例:匿名 + 具名 + 作用域插槽三合一

封装一个 Table 表格组件:

  • header 具名插槽:表头自定义
  • 匿名 children:表格额外说明
  • cellSlot 作用域插槽:单元格自定义渲染,子组件暴露行数据

Table.jsx

复制代码
export default function Table({ header, children, cellSlot, tableData }) {
  return (
    <div style={{ border: "1px solid #999" }}>
      {/* 具名插槽 header */}
      {header && <div style={{ background: "#f5f5f5" }}>{header}</div>}

      {/* 匿名插槽 */}
      <div style={{ padding: 4 }}>{children}</div>

      <div>
        {tableData.map((row) => (
          <div key={row.id} style={{ display: "flex", gap: 20 }}>
            {/* 作用域插槽,传递行数据 row */}
            {cellSlot(row)}
          </div>
        ))}
      </div>
    </div>
  );
}

父组件调用

复制代码
import Table from "./Table";

export default function Demo() {
  const tableData = [
    { id: 1, title: "订单1", status: 1 },
    { id: 2, title: "订单2", status: 0 },
  ];

  return (
    <Table
      header={<h4>订单列表(具名插槽头部)</h4>}
      tableData={tableData}
      {/* 作用域插槽,接收子组件抛出的 row */}
      cellSlot={(row) => (
        <>
          <p>{row.title}</p>
          <p>{row.status ? "已完成" : "待处理"}</p>
        </>
      )}
    >
      {/* 匿名插槽 */}
      <p>温馨提示:状态0代表未完成</p>
    </Table>
  );
}

对比 Vue 插槽速记

表格

插槽类型 Vue 实现 React 等价实现
匿名插槽 <slot /> props.children
具名插槽 <slot name="header"/> props 传 JSX:<Comp header={<div/>}>
作用域插槽 <template #x="scope"> props 传渲染函数:x={(data)=>JSX}

关键注意点

  1. children 可以是单个元素、数组、文本,自动对应匿名插槽;
  2. 具名插槽建议用清晰命名 props,复杂组件可封装对象统一管理;
  3. 作用域插槽必须传函数,只有函数才能让子组件把内部数据回传给父;
  4. React 无模板语法,全部 JSX 原生实现,灵活性高于 Vue 插槽。