65.9K
CodeProject 正在变化。 阅读更多。
Home

使用插槽将多个子项传递给 React 组件

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2018年7月31日

CPOL

4分钟阅读

viewsIcon

23572

如何使用插槽将多个子项传递给 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 日发布。

© . All rights reserved.