现前端的工程化越发重要,虽然使用Ctrl+C与Ctrl+V同样能够完成需求,但是一旦面临修改那就是一项庞大的任务,于是减少代码的拷贝,增加封装复用能力,实现可维护、可复用的代码就变得尤为重要,在React中组件是代码复用的主要单元,基于组合的组件复用机制相当优雅,而对于更细粒度的逻辑(状态逻辑、行为逻辑等),复用起来却不那么容易,很难把状态逻辑拆出来作为一个可复用的函数或组件,实际上在Hooks出现之前,都缺少一种简单直接的组件行为扩展方式,对于Mixin、HOC、Render Props都算是在既有(组件机制的)游戏规则下探索出来的上层模式,一直没有从根源上很好地解决组件间逻辑复用的问题,直到Hooks登上舞台,下面我们就来介绍一下Mixin、HOC、Render Props、Hooks四种组件间复用的方式。
Mixin当然React很久之前就不再建议使用Mixin作为复用的解决方案,但是现在依旧能通过create-react-class提供对Mixin的支持,此外注意在以ES6的class方式声明组件时是不支持Mixin的。
Mixins允许多个React组件之间共享代码,它们非常类似于Python中的mixins或PHP中的traits,Mixin方案的出现源自一种OOP直觉,只在早期提供了React.createClass() API(React v15.5.0正式废弃,移至create-react-class)来定义组件,自然而然地,(类)继承就成了一种直觉性的尝试,而在JavaScript基于原型的扩展模式下,类似于继承的Mixin方案就成了一个不错的解决方案,Mixin主要用来解决生命周期逻辑和状态逻辑的复用问题,允许从外部扩展组件生命周期,在Flux等模式中尤为重要,但是在不断实践中也出现了很多缺陷:
组件与Mixin之间存在隐式依赖(Mixin经常依赖组件的特定方法,但在定义组件时并不知道这种依赖关系)。
多个Mixin之间可能产生冲突(比如定义了相同的state字段)。
Mixin倾向于增加更多状态,这降低了应用的可预测性,导致复杂度剧增。
隐式依赖导致依赖关系不透明,维护成本和理解成本迅速攀升。
难以快速理解组件行为,需要全盘了解所有依赖Mixin的扩展行为,及其之间的相互影响
组件自身的方法和state字段不敢轻易删改,因为难以确定有没有Mixin依赖它。
Mixin也难以维护,因为Mixin逻辑最后会被打平合并到一起,很难搞清楚一个Mixin的输入输出。
毫无疑问,这些问题是致命的,所以,Reactv0.13.0放弃了Mixin静态横切(类似于继承的复用),转而走向HOC高阶组件(类似于组合的复用)。
示例上古版本示例,一个通用的场景是: 一个组件需要定期更新,用setInterval()做很容易,但当不需要它的时候取消定时器来节省内存是非常重要的,React提供生命周期方法来告知组件创建或销毁的时间,下面的Mixin,使用setInterval()并保证在组件销毁时清理定时器。
var SetIntervalMixin = { componentWillMount: function() { this.intervals = []; }, setInterval: function() { this.intervals.push(setInterval.apply(null, arguments)); }, componentWillUnmount: function() { this.intervals.forEach(clearInterval); } }; var TickTock = React.createClass({ mixins: [SetIntervalMixin], // 引用 mixin getInitialState: function() { return {seconds: 0}; }, componentDidMount: function() { this.setInterval(this.tick, 1000); // 调用 mixin 的方法 }, tick: function() { this.setState({seconds: this.state.seconds + 1}); }, render: function() { return ( <p> React has been running for {this.state.seconds} seconds. </p> ); } }); ReactDOM.render( <TickTock />, document.getElementById("example") ); HOCMixin之后,HOC高阶组件担起重任,成为组件间逻辑复用的推荐方案,高阶组件从名字上就透漏出高级的气息,实际上这个概念应该是源自于JavaScript的高阶函数,高阶函数就是接受函数作为输入或者输出的函数,可以想到柯里化就是一种高阶函数,同样在React文档上也给出了高阶组件的定义,高阶组件是接收组件并返回新组件的函数。具体的意思就是: 高阶组件可以看作React对装饰模式的一种实现,高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,他会返回一个增强的React组件,高阶组件可以让我们的代码更具有复用性,逻辑性与抽象性,可以对render方法进行劫持,也可以控制props与state等。
对比Mixin与HOC,Mixin是一种混入的模式,在实际使用中Mixin的作用还是非常强大的,能够使得我们在多个组件中共用相同的方法,但同样也会给组件不断增加新的方法和属性,组件本身不仅可以感知,甚至需要做相关的处理(例如命名冲突、状态维护等),一旦混入的模块变多时,整个组件就变的难以维护,Mixin可能会引入不可见的属性,例如在渲染组件中使用Mixin方法,给组件带来了不可见的属性props和状态state,并且Mixin可能会相互依赖,相互耦合,不利于代码维护,此外不同的Mixin中的方法可能会相互冲突。之前React官方建议使用Mixin用于解决横切关注点相关的问题,但由于使用Mixin可能会产生更多麻烦,所以官方现在推荐使用HOC。高阶组件HOC属于函数式编程functional programming思想,对于被包裹的组件时不会感知到高阶组件的存在,而高阶组件返回的组件会在原来的组件之上具有功能增强的效果,基于此React官方推荐使用高阶组件。
HOC虽然没有那么多致命问题,但也存在一些小缺陷: