使用插槽将多个子项传递给 React 组件
如何使用插槽将多个子项传递给 React 组件
你需要创建一个可复用的组件。但是 `children` prop 无法满足要求。这个组件需要能够接受*多个*子组件,并按照自己的方式将它们放置在布局中 - 而不是紧挨着彼此。
也许你正在创建一个带有 header、sidebar 和 content area 的 `Layout`。也许你正在编写一个带有需要动态显示的左侧和右侧的 `NavBar`。
这些情况都可以通过“插槽”模式轻松实现 – 也就是将 JSX 传递到 prop 中。
要点:你可以将 JSX 传递到*任何* prop 中,不仅仅是名为 `children` 的 prop,也不仅仅是通过将 JSX 嵌套在组件的 tag 中 - 它可以简化数据传递并使组件更具可复用性。
React Children 快速回顾
为了确保我们都在同一页面上:React 允许你通过将 *children* 嵌套在其 `JSX` tag 中来将它们传递给组件。这些元素(零个、一个或多个)在该组件内作为名为 `children` 的 prop 提供。 React 的 `children` prop 类似于 Angular 的 transclusion 或 Vue 的 `<slot>`。
这是一个将 children 传递给 `Button` 组件的示例
<Button>
<Icon name="dollars"/>
<span>BUY NOW</span>
</Button>
让我们放大 `Button` 的实现,看看它如何处理 children
function Button(props) {
return (
<button>
{props.children}
</button>
);
}
`Button` 实际上只是用一个 `button` 元素包装你传入的内容。这里没有什么突破性的东西,但这是一个有用的能力。它使接收组件能够将 children 放置在布局中的任何位置,或者将它们包装在 `className` 中进行样式设置。渲染的 HTML 输出可能看起来像这样
<button>
<i class="fa fa-dollars"></i>
<span>BUY NOW</span>
</button>
(顺便说一句,这假设 `Icon` 组件渲染了 `<i>` tag)。
Children 也是一个普通的 Prop
这是 React 处理 children 方式的一个很酷的地方:嵌套元素被分配给 `children` prop,但它不是一个神奇的特殊 prop。你可以像分配其他 prop 一样分配它。请看
// This code...
<Button children={<span>Click Me</span>} />
// Is equivalent to this code...
<Button>
<span>Click Me</span>
</Button>
所以你不仅可以将 `children` 作为常规 prop 传递,而且你可以将 *JSX* 传递到 prop 中? 什么!
是的。而且这种能力不仅仅适用于名为“`children`”的 prop…
使用 Props 作为命名插槽
如果我告诉你,你可以将 JSX 传递到*任何* prop 中呢?
(你已经发现了,不是吗。)
这是一个这些“插槽” props 在工作中的示例 - 使用 3 个 props 调用一个名为 `Layout` 的组件
<Layout
left={<Sidebar/>}
top={<NavBar/>}
center={<Content/>}
/>
在 `Layout` 组件内部,它可以对 `left`、`top` 和 `center` props 做任何它需要做的事情。这是一个简单的例子
function Layout(props) {
return (
<div className="layout">
<div className="top">{props.top}</div>
<div className="left">{props.left}</div>
<div className="center">{props.center}</div>
</div>
);
}
你可以想象 `Layout` 在内部会更加复杂,包含许多嵌套的 `div` 或 Bootstrap 类用于样式设置或其他任何东西。或者它可以将这些部分传递给专门的组件。 无论 `Layout` 需要做什么,它的用户只需要担心传入这 3 个 props `left`、`top` 和 `center`。
使用 Children 直接传递 Props
将 children 作为 prop 传递(无论是正确的 `children` 还是其他 prop)的另一个好处是:在你传入 child prop 的那一刻,你处于*父组件*的作用域中,所以你可以传递任何你需要的东西。
这就像*跳过一个级别*。例如:不必将“`user`”传递给 `Layout` 并让 `Layout` 将“`user`”传递给 `NavBar`,你可以创建一个 `NavBar`(用户已经设置好)并将整个东西传递给 `Layout`。
这可以帮助避免“prop 钻取”问题,即你必须通过多个层级传递一个 prop。
function App({ user }) {
return (
<div className="app">
<Nav>
<UserAvatar user={user} size="small" />
</Nav>
<Body
sidebar={<UserStats user={user} />}
content={<Content />}
/>
</div>
);
}
// Accept children and render it/them
const Nav = ({ children }) => (
<div className="nav">
{children}
</div>
);
// Body needs a sidebar and content, but written this way,
// they can be ANYTHING
const Body = ({ sidebar, content }) => (
<div className="body">
<Sidebar>{sidebar}</Sidebar>
{content}
</div>
);
const Sidebar = ({ children }) => (
<div className="sidebar">
{children}
</div>
);
const Content = () => (
<div className="content">main content here</div>
);
现在将其与此解决方案进行比较,其中 `Nav` 和 `Body` 接受 `user` prop,然后负责手动将其传递给它们的 children,而这些 children 必须将其传递给*他们的* children…
function App({ user }) {
return (
<div className="app">
<Nav user={user} />
<Body user={user} />
</div>
);
}
const Content = () => <div className="content">main content here</div>;
const Sidebar = ({ user }) => (
<div className="sidebar">
<UserStats user={user} />
</div>
);
const Body = ({ user }) => (
<div className="body">
<Sidebar user={user} />
<Content user={user} />
</div>
);
const Nav = ({ user }) => (
<div className="nav">
<UserAvatar user={user} size="small" />
</div>
);
不太好,对吧? 以这种方式向下传递 props(又名“prop 钻取”)比你想要的更紧密地耦合组件 - 这并不总是坏事,但最好有意识地了解。 使用前面示例中的 children 可以避免必须寻找更复杂的解决方案,例如 context、Redux 或 MobX(仅举几例)。
请注意 PureComponent / shouldComponentUpdate
如果你需要在接受 children 的组件上实现 `shouldComponentUpdate` (或 `PureComponent`),并且它阻止了重新渲染,那么它也会阻止 children 渲染。所以,只要记住这一点。 在实践中,具有“插槽”的组件可能很可能是最小且快速渲染的,因此不太可能需要性能优化。
如果你遇到需要优化“插槽”组件的性能的情况,请考虑将性能较慢的部分提取到单独的组件中,并独立优化它。
使用插槽向 React 组件传递多个子组件 最初由 Dave Ceddia 于 Dave Ceddia 于 2018 年 7 月 31 日发布。