使用 children
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);
这transclusion
在Angular中也称为。
children
是React中的一个特殊道具,它将包含组件标签中的内容(这里<App name={name}/>
是内部Wrapper
,因此它是children
请注意,您不一定需要使用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函数传递给组件,通常将这种模式称为render prop
,并且children
prop通常用于提供该回调。
此模式并非真正用于布局。包装器组件通常用于保存和管理某些状态,并将其注入其渲染函数中。
反例:
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 ...),因为人们倾向于比HOC更容易找到此API。react-powerplug是简单的render-prop组件的集合。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);
一个高阶组件/ HOC通常是一个函数,它的成分,并返回一个新的组件。
使用高阶组件可能比使用children
或具有更高的性能render props
,这是因为包装器可以缩短渲染步骤的能力shouldComponentUpdate
。
在这里我们正在使用PureComponent
。重新渲染应用程序时,如果WrappedApp
名称prop不会随时间变化,则包装程序可以说“我不需要渲染,因为prop(实际上,名称)与以前相同”。使用上述children
基于基础的解决方案,即使包装器为PureComponent
,也不是这样,因为每次父级呈现时都会重新创建children元素,这意味着即使包装的组件是纯净的,包装器也将始终会重新呈现。有一个babel插件可以帮助缓解这种情况并确保children
元素随时间推移保持不变。
结论
高阶组件可以为您提供更好的性能。它并不那么复杂,但是起初看起来确实不友好。
阅读本文后,请勿将整个代码库迁移到HOC。请记住,出于性能原因,在应用程序的关键路径上,您可能希望使用HOC而不是运行时包装器,特别是如果多次使用同一包装器,则值得考虑将其设为HOC。
Redux最初使用运行时包装<Connect>
,后来connect(options)(Comp)
出于性能原因切换到HOC (默认情况下,包装是纯净的并使用shouldComponentUpdate
)。这是我在此答案中要强调的内容的完美例证。
请注意,如果组件具有render-prop API,通常很容易在其之上创建HOC,因此,如果您是lib作者,则应首先编写render prop API,并最终提供HOC版本。这是Apollo对<Query>
render-prop组件所做的,graphql
HOC也在使用它。
就我个人而言,我同时使用这两种方法,但是在有疑问时,我更喜欢HOC,因为:
compose(hoc1,hoc2)(Comp)
与渲染道具相比,组合它们()更惯用
- 它可以给我更好的表现
- 我熟悉这种编程风格
我会毫不犹豫地使用/创建我喜欢的工具的HOC版本:
- 反应的
Context.Consumer
补偿
- 未声明的
Subscribe
- 使用
graphql
Apollo的HOC代替Query
渲染道具
在我看来,有时渲染道具会使代码更具可读性,有时则使代码更具可读性...我尝试根据我的约束使用最实用的解决方案。有时可读性比性能更重要,有时则不那么重要。明智地选择,不要束缚地遵循2018年将一切都转换为render-props的趋势。