。如何定义 Wrapper 组件以实现相同的输出?" /> 。如何定义 Wrapper 组件以实现相同的输出?"> 。如何定义 Wrapper 组件以实现相同的输出?" />
许多模板语言都有“slots”或“yield”语句,允许执行某种控制反转来将一个模板包装在另一个模板中。
Angular 有 "transclude" option。
Rails 有 yield statement。如果 React.js 有 yield 语句,它看起来像这样:
var Wrapper = React.createClass({
render: function() {
return (
<div className="wrapper">
before
<yield/>
after
</div>
);
}
});
var Main = React.createClass({
render: function() {
return (
<Wrapper><h1>content</h1></Wrapper>
);
}
});
期望的输出:
<div class="wrapper">
before
<h1>content</h1>
after
</div>
唉,React.js 没有 <yield/>
。如何定义 Wrapper 组件以实现相同的输出?
尝试:
var Wrapper = React.createClass({
render: function() {
return (
<div className="wrapper">
before
{this.props.children}
after
</div>
);
}
});
有关详细信息,请参阅文档中的 Multiple Components: Children 和 Type of the Children props。
使用儿童
const Wrapper = ({children}) => (
<div>
<div>header</div>
<div>{children}</div>
<div>footer</div>
</div>
);
const App = ({name}) => <div>Hello {name}</div>;
const WrappedApp = ({name}) => (
<Wrapper>
<App name={name}/>
</Wrapper>
);
render(<WrappedApp name="toto"/>,node);
这在 Angular 中也称为 transclusion
。
children
是 React 中的一个特殊道具,它将包含组件标签内的内容(这里 <App name={name}/>
在 Wrapper
内,所以它是 children
请注意,您不一定需要使用组件独有的 children
,如果需要,您也可以使用普通 props,或者混合使用 props 和 children:
const AppLayout = ({header,footer,children}) => (
<div className="app">
<div className="header">{header}</div>
<div className="body">{children}</div>
<div className="footer">{footer}</div>
</div>
);
const appElement = (
<AppLayout
header={<div>header</div>}
footer={<div>footer</div>}
>
<div>body</div>
</AppLayout>
);
render(appElement,node);
这对于许多用例来说既简单又很好,我建议大多数消费者应用程序都这样做。
渲染道具
可以将渲染函数传递给组件,这种模式通常称为 render prop
,并且 children
属性通常用于提供该回调。
这种模式并不是真正意义上的布局。包装器组件通常用于保存和管理某些状态并将其注入其渲染函数中。
反例:
const Counter = () => (
<State initial={0}>
{(val, set) => (
<div onClick={() => set(val + 1)}>
clicked {val} times
</div>
)}
</State>
);
你可以变得更花哨,甚至提供一个对象
<Promise promise={somePromise}>
{{
loading: () => <div>...</div>,
success: (data) => <div>{data.something}</div>,
error: (e) => <div>{e.message}</div>,
}}
</Promise>
请注意,您不一定需要使用 children
,这是一个品味/API 的问题。
<Promise
promise={somePromise}
renderLoading={() => <div>...</div>}
renderSuccess={(data) => <div>{data.something}</div>}
renderError={(e) => <div>{e.message}</div>}
/>
时至今日,许多库都在使用渲染道具(React 上下文、React-motion、Apollo ......),因为人们倾向于发现这个 API 比 HOC 更容易。 react-powerplug 是一组简单的渲染道具组件。 react-adopt 帮助您进行作曲。
高阶组件(HOC)。
const wrapHOC = (WrappedComponent) => {
class Wrapper extends React.PureComponent {
render() {
return (
<div>
<div>header</div>
<div><WrappedComponent {...this.props}/></div>
<div>footer</div>
</div>
);
}
}
return Wrapper;
}
const App = ({name}) => <div>Hello {name}</div>;
const WrappedApp = wrapHOC(App);
render(<WrappedApp name="toto"/>,node);
Higher-Order Component / HOC 通常是一个接受一个组件并返回一个新组件的函数。
使用高阶组件可能比使用 children
或 render props
的性能更高,因为包装器可以使用 shouldComponentUpdate
提前一步使呈现短路。
这里我们使用 PureComponent
。重新渲染应用程序时,如果 WrappedApp
名称道具不随时间变化,包装器可以说“我不需要渲染,因为道具(实际上是名称)与以前相同”。使用上面基于 children
的解决方案,即使包装器是 PureComponent
,情况也并非如此,因为每次父渲染时都会重新创建子元素,这意味着包装器可能总是重新渲染,即使包装的组件是纯的。有一个 babel plugin 可以帮助缓解这种情况并确保随着时间的推移保持不变的 children
元素。
结论
高阶组件可以为您提供更好的性能。它并没有那么复杂,但一开始它肯定看起来不友好。
阅读本文后,不要将整个代码库迁移到 HOC。请记住,在应用程序的关键路径上,出于性能原因,您可能希望使用 HOC 而不是运行时包装器,特别是如果多次使用相同的包装器,则值得考虑将其设为 HOC。
Redux 最初使用运行时包装器 <Connect>
,后来出于性能原因切换到 HOC connect(options)(Comp)
(默认情况下,包装器是纯的并使用 shouldComponentUpdate
)。这是我想在这个答案中强调的完美例证。
请注意,如果一个组件有一个 render-prop API,通常很容易在它之上创建一个 HOC,所以如果你是一个 lib 作者,你应该先编写一个 render prop API,并最终提供一个 HOC 版本。这就是 Apollo 对 <Query>
render-prop 组件以及使用它的 graphql
HOC 所做的。
就个人而言,我两者都用,但如果有疑问,我更喜欢 HOC,因为:
与渲染道具相比,组合它们 (compose(hoc1,hoc2)(Comp)) 更惯用
它可以给我更好的表现
我熟悉这种编程风格
我会毫不犹豫地使用/创建我最喜欢的工具的 HOC 版本:
React 的 Context.Consumer comp
未说明的订阅
使用 Apollo 的 graphql HOC 代替 Query 渲染道具
在我看来,有时 render props 会使代码更具可读性,有时则更少……我尝试根据自己的限制使用最实用的解决方案。有时可读性比性能更重要,有时则不然。明智地选择,不要拘泥于 2018 年将所有东西都转换为渲染道具的趋势。
除了 Sophie 的回答之外,我还发现了发送子组件类型的用途,执行如下操作:
var ListView = React.createClass({
render: function() {
var items = this.props.data.map(function(item) {
return this.props.delegate({data:item});
}.bind(this));
return <ul>{items}</ul>;
}
});
var ItemDelegate = React.createClass({
render: function() {
return <li>{this.props.data}</li>
}
});
var Wrapper = React.createClass({
render: function() {
return <ListView delegate={ItemDelegate} data={someListOfData} />
}
});
delegate
的任何文档,您是如何找到它的?