共计 2645 个字符,预计需要花费 7 分钟才能阅读完成。
在接触过 React 项目后,大多数人都应该已经了解过或则用过了 HOC(High-Order-Components) 和 FaCC(Functions as Child Components),因为这两个模式在大多数 react 的开源库里都存在。比如 react-router 里面的 withRouter 就是典型的高阶组件,接受一个组件返回另外一个经过增强后的组件。而 react-motion 中的 Motion 就是典型的 FaCC 的应用。
HOC 和 FaCC 两者做的事也是非常相似的,都是类似设计模式里面的装饰者模式。都是在原有的实例或则单元上进行功能的增强。
当然不只是一些开源库中会使用,在平常的代码编写中,也有很多地方是适用于使用 HOC 和 FaCC 去封装一些逻辑。比如数据埋点,新特性的 toggle,获取转换数据等。对于增强代码可读性和逻辑复用来说,非常有用的。
HOC
高阶函数我们都用过,就是接受一个函数然后返回一个经过封装的函数:
const plus = first => second => (first + second)
plus(1)(2) // 3
而高阶组件就是高阶函数的概念应用到高阶组件上:
const withClassName = ComposedComponent => props => (
<ComposedComponent {…props} className=’demo-class’ />
)
// 使用
const Header = text => (<header>{text}</header>)
const headerWitheClass = withClassName(Header)
接受一个组件返回一个经过包装的新组件。在我们经常使用的 withRouter 就是在原有组件 props 上面在加上 localtion 等属性。除了添加 props 以外高阶组件还能做到:
在真正调用组件前后做一些事,比如埋点数据等
判断组件是否该 render,或则应该 render 其他的东西,比如出错之后 render 错误页面
传递 props 并增加新的 props
不 render 组件,转而做一些其他的事情,比如渲染一个外部的 dom
对于上面的前三点都比较好理解,解释一下第 4 点。比如你在 render 了一个页面之后,需要改变一下页面的 title. 这是单页应用普遍存在的一个需求,通常你可以在具体 router 库中使用 hook 去实现。当然也可以通过 HOC 来实现:
const withTitleChange = ComposedComponent => {
return class extends React.Component {
componentDidMount () {
const {title} = this.props
document.title = title
}
render () {
const props = this.props
return <ComposedComponent {…props} />
}
}
}
FaCC
同样 FaCC 也是用于增强原有组件能力的一种模式,其主要功能的实现在于 react 的 props.children 可以是任何东西,包括函数。我们可以拿上面 class 的例子用 FaCC 再实现一遍:
const ClassNameWrapper = ({children}) => children(‘demo-class’)
// 使用
const HeadWithClass = (props) => (
<ClassNameWrapper>
{(class) => <header classNmae={class} ></header>}
</ClassNameWrapper>
)
在 FaCC 中你也可以像 HOC 一样在生命周期中做很多事对原有的组件进行封装,基本上 HOC 能做的 FaCC 也都能做。我所在的项目之前都是大范围的使用 HOC,再经过一番讨论后,开始大范围的转变成 FaCC。
区别
两者都是用来增强原有组件的,具体该使用那种?那种是正确的模式?社区关于这一点也有很多讨论,比如就有人说 FaCC 是反模式:Function as Child Components Are an Anti-Pattern。他给出的理由是 children 并不语义化,会造成困惑,然后他提出了 Component Injection 的模式,有兴趣的同学可以读一读。
具体从几个方面做一下对比:
组合阶段
组合阶段意思就是 HOC,FaCC 和要被增强的组件的组合时候。可以很明显发现,FaCC 对于前后组件对接依赖信息显示的更多,相对而言更容易理解。而 HOC,相互之间如何桥接,你必须得深入到 HOC 内部读代码才可以知道这个 HOC 具体干了啥。
// HOC example
import View from ‘./View’
const DetailPage = withServerData(withNavigator(View))
// FaCC example
import View from ‘./View’
const DetailPage = props => (
<FetchServerData>
{
data => (
<Navigator>
<View data={data} {…props} />
</Navigator>
)
}
</FetchServerData>
)
如果在上面再增加 2 个 HOC,上面组合的过程就变得十分难看。而 FaCC 相对而言,如何封装,数据源来自那里,组件接受了那些数据都比较显眼。
性能优化
在 HOC 中我们能接受到宿主的 prop,因为 props 是从 HOC 往下传递的,所以我们也有完整的生命周期,我们可以使用 shouldComponentUpdate 优化。而 FaCC 则不然,无法在其内部做比较 props,除非在组合的时候外部在包一个组件才能进行比较 props。
灵活性
FaCC 在组合阶段相对 HOC 更为灵活,他并不规定被增强组件如何使用它传递下去的属性。而 HOC 基本上在编写完后就定死了。
另外,FaCC 不会再去创建一个新的 Component,而 HOC 会创建一个新的 Component 然后传递 props 下去。
总结
社区中很多开源库已经使用了两种模式,也有很多的文章进行比较。也有很多激烈讨论,当然对于最后解决问题而言,两种模式都有好处。出于不同的考虑,可能选择不一样。
参考文章:
http://rea.tech/functions-as-…
http://rea.tech/reactjs-real-…
https://medium.com/merrickchr…
http://www.ituring.com.cn/boo… 第四章