React 高阶组件与render props
HOC
高阶组件:React组件复用中的一种技巧,基于组合特性的设计模式。具体而言就是一个入参为组件,并返回一个新组件的函数。
function higherOrderComponent(Component) {
return <Component name="HOC" />
}
const EnhancedComponent = higherOrderComponent(BaseComponent)
高阶组件的设计模式是为了解决横切关注点(cross-cutting concerns)问题。
关注点:通过功能划分模块的系统中的一部分。也就是某种小功能。横切关注点:某个专注点在多个模块中都出现,就是横切关注点。例如日志功能,在系统的多个模块都会出现,同时又难以通过传统的模块化方式进行管理,就是一个横切关注点。
当需要在多个组件之间共享一些抽象逻辑时,传统的组件封装模式不太适用,例如数据订阅,发布,更新状态等。这些逻辑可能大量出现多个组件中,但又难以通过继承或模块化的方式将代码集中管理——代码散乱,分布在在多个组件生命周期中,函数组件没有生命周期方法等。
HOC则是将这些散落的逻辑集中到一个抽象的容器组件中,该容器组件通过props将数据传递给子组件,容器组件自身并不关心子组件的内部实现以及这些数据如何使用,仅处理这些散乱的公共逻辑。HOC本身只是一个纯函数。
render props
react开发过程中的一种术语,指一个组件的props存在一个返回react元素的函数属性,该组件在内部调用该函数来实现自身的渲染逻辑。
<DataProvider render={data => (<div>{data}</div>)} />
render props同样是用来解决横切关注点问题。将公共逻辑封装成一个组件实现逻辑共享,利用props实现差异化的渲染。
HOC vs Render Props
既然都是为了解决横切关注点问题,两者有什么区别?为何有了HOC后还需要render props?
事实上两者只是实现逻辑共享的两个方向,并没由严格意义上的谁优谁劣,大部分场景下开发者可以更具自己的喜好选择使用,正如react官网描述的,你可以很方便的通过带有render props的常规组件实现一个HOC。
至于为何有了HOC后还需要render props,或许可以从两者的设计角度窥探一二。
HOC的设计模式是一种静态的组合,将需要复用的核心逻辑封装到一个容器组件中,变更部分留给被包裹组件处理。由于是通过纯函数来增强被包裹的组件,那么理想状态下,可以通过一个组合工具函数来无止境的增强被包裹函数。例如redux的compose utility方法。
function withFoo(Base) {
return class extends React.Component {
constructor(props){
super(props)
this.props = Object.assign(props, {foo: 'enhanceByFoo'})
}
render() {
return <Base {...this.props} />
}
}
}
function withBar(Base) {
return class extends React.Component {
constructor(props) {
super(props)
this.props = Objec.assign(props, {bar: 'enhanceByBar'})
}
render() {
return <Base {this.props} />
}
}
}
function Base(props) {
const {children, ...enhancedProps} = props
return (
<div>
{Object.keys(enhancedProps).map(key => <div key={key}>{enhancedProps[key]}</div>)}
{children}
</div>
)
}
function compose(...args) {
args = args ?? []
return function (Component) {
return args.reduce((pre, enhancer) => typeof enhancer === 'function' ? enhancer(pre) : pre, Component)
}
}
const EnhancedComponent = compose(withFoo, withBar)(Base)
基于静态组合概念的HOC虽然能够任意增强组件,但无法利用react运行时的生命周期方法(HOC不能在render方法中使用);不同HOC之间可能存在命名冲突,用来增强同一个组件时只会有一个生效。
该讨论提及HOC的由来以及HOC与装饰器之间的差异:Higher-order Components。根据react官网建议——不要修改被包裹组件,而是组合它。也就是像传统装饰器一样使用HOC。
相对而言,render props是基于动态组合的概念,将共享逻辑封装到一个组件中,动态变更的内容通过属性回调的方式进行处理。而props内容是可以在运行时进行逻辑变更的。
class Base extends React.Component {
constructor(props) {
super(props)
}
render(){
return <Common render={data => {
return this.props.enhancer === 'foo' ? <Foo data={data} /> : <Bar data={data} />
}} />
}
}
但是,当存在多层render props时,通过属性回调的组合方式容易形成类似“回调地狱”的层级结构,增加代码维护负担。同时,动态组合的方式在每次重渲染过程中都会生成新的回调函数,存在难以优化的性能问题——如上面例子中的Common组件,每次渲染都会进行更新(render属性永远是新生成的函数)。
综上,如果应用场景是需要方便对组件进行多种功能的组合,HOC可能更为方便;如果只进行一个两种功能的共享,无需考虑性能场景时,render props是更为快捷的实现方式——没有HOC那么多固有模板,本质只是一个组件的属性。