React 实现插槽完整示例(匿名 / 具名 / 作用域插槽)
先说明核心原理: Vue 的插槽本质是预留渲染位置 + 传递渲染内容 ,React 没有原生 slot 语法,靠两类方案实现:
- children 属性:实现匿名插槽
- props 传递 JSX 节点:实现具名插槽
- 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} |
关键注意点
children可以是单个元素、数组、文本,自动对应匿名插槽;- 具名插槽建议用清晰命名 props,复杂组件可封装对象统一管理;
- 作用域插槽必须传函数,只有函数才能让子组件把内部数据回传给父;
- React 无模板语法,全部 JSX 原生实现,灵活性高于 Vue 插槽。