React.js:将一个组件包装到另一个组件中

许多模板语言都有“插槽”或​​“合格”语句,这些语句允许做一些控制反转来将一个模板包装到另一个模板中。

Angular有“transclude”选项 。

Rails有收益声明 。 如果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> ); } }); 

请参阅文档中的多个组件:儿童和儿童 types以获取更多信息。

最简单的解决scheme:运行时包装

 const Wrapper = ({children}) => ( <div> <div>header</div> <div>{children}</div> <div>footer</div> </div> ); const App = ({name}) => <div>Hello {name}</div>; const WrappedApp = () => ( <Wrapper> <App/> </Wrapper> ); 

这个解决scheme是最习惯的,但是Wrapper每次都要收到一个新的children道具。 不要担心,90%的用例当然是好的)。

更好的解决scheme:高阶组件(HOC)。

 const wrapHOC = (WrappedComponent) => (props) => ( <div> <div>header</div> <div><WrappedComponent {...props}/></div> <div>footer</div> </div> ); const App = ({name}) => <div>Hello {name}</div>; const WrappedApp = wrapHOC(App); 

为了获得更好的性能,使用高阶组件/ HOC (现在正式添加到React文档中 )比运行时包装更好,因为HOC通常可以使用shouldComponentUpdate更快地将渲染shouldComponentUpdate

在这里我保持简单的例子,但显然如果你想利用这个性能增益,你应该使用一个PureComponent而不是一个无状态的function组件(令人惊讶的是不会优化与shouldComponentUpdate

 const wrapHOC = (WrappedComponent) => (props) => { class Wrapper extends React.PureComponent { render() { return ( <div> <div>header</div> <div><WrappedComponent {...this.props}/></div> <div>footer</div> </div> ); } } return Wrapper; } 

现在,如果name道具不改变,HOC将能够将渲染短路一步。

其他奇特的包装模式:

孩子可以是任何东西 :它可能会令人惊讶,但你可以随意使用任何你想要的作为children道具。 一些相当普遍的做法是使用函数children 。

 <State initial={0}> {(val, set) => ( <div onClick={() => set(val + 1)}> clicked {val} times </div> )} </State> 

你可以变得更花哨,甚至提供一个对象

 <Promise promise={getSomeAsyncData()}> {{ loading: () => <div>...</div>, success: (data) => <div>{data.something}</div>, error: (e) => <div>{e.message}</div>, }} </Promise > 

HOC的论据可以是任何东西

以同样的方式,您可以自由地为您的HOC提供额外的参数。 请查看使用mapStateToProps函数和选项的react-redux的 connect示例。

您甚至可以将儿童function和HOC混合在一起:以下是Guillermo Rauch开源博客中的示例用法: HOC / 用法

 export default withViews(({ views }) => ( <div> This blog post has been viewed {views} times ..... </div> ) 

结论

更高级的组件可以给你更好的性能。 这并不复杂,但起初看起来并不友好。

阅读完此代码后,不要将整个代码库迁移到HOC。 请记住,在应用程序的关键path中,出于性能方面的考虑,您可能希望使用HOC而不是运行时包装程序,特别是如果使用相同的包装程序很多次,则值得考虑将其作为HOC。

Redux首先使用一个运行时包装器<Connect>然后为了性能的原因切换到HOC connect(options)(Comp) 。 这是我想在这个答案中突出显示的完美例证。

除了Ben的回答之外,我还发现了一个用于发送子元素types的用法,就是这样做的:

 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} /> } });