基本每个开发者都需要考虑逻辑复用的问题,否则你的项目中将充斥着大量的重复代码。那么 React
是怎么复用组件逻辑的呢?本文将一一介绍 React
复用组件逻辑的几种方法,希望你读完之后能够有所收获。如果你对这些内容已经非常清楚,那么略过本文即可。
我已尽量对文中的代码和内容进行了校验,但是因为自身知识水平限制,难免有错误,欢迎在评论区指正。
1. Mixins
Mixins
事实上是 React.createClass
的产物了。当然,如果你曾经在低版本的 react
中使用过 Mixins
,例如 react-timer-mixin
, react-addons-pure-render-mixin
,那么你可能知道,在 React
的新版本中我们其实还是可以使用 mixin
,虽然 React.createClass
已经被移除了,但是仍然可以使用第三方库 create-react-class
,来继续使用 mixin
。甚至,ES6 写法的组件,也同样有方式去使用 mixin
。当然啦,这不是本文讨论的重点,就不多做介绍了,如果你维护的老项目在升级的过程中遇到这类问题,可以与我探讨。
新的项目中基本不会出现 Mixins
,但是如果你们公司还有一些老项目要维护,其中可能就应用了 Mixins
,因此稍微花点时间,了解下 Mixins
的使用方法和原理,还是有必要的。倘若你完全没有这方面的需求,那么跳过本节亦是可以的。
Mixins 的使用
React 15.3.0
版本中增加了 PureComponent
。而在此之前,或者如果你使用的是 React.createClass
的方式创建组件,那么想要同样的功能,就是使用 react-addons-pure-render-mixin
,例如:
//下面代码在新版React中可正常运行,因为现在已经无法使用 `React.createClass`,我就不使用 `React.createClass` 来写了。
const createReactClass = require('create-react-class');
const PureRenderMixin = require('react-addons-pure-render-mixin');
const MyDialog = createReactClass({
displayName: 'MyDialog',
mixins: [PureRenderMixin],
//other code
render() {
return (
<div>
{/* other code */}
</div>
)
}
});
首先,需要注意,mixins
的值是一个数组,如果有多个 Mixins
,那么只需要依次放在数组中即可,例如: mixins: [PureRenderMixin, TimerMixin]
。
Mixins 的原理
Mixins
的原理可以简单理解为将一个 mixin
对象上的方法增加到组件上。类似于 $.extend
方法,不过 React
还进行了一些其它的处理,例如:除了生命周期函数外,不同的 mixins
中是不允许有相同的属性的,并且也不能和组件中的属性和方法同名,否则会抛出异常。另外即使是生命周期函数,constructor
、render
和 shouldComponentUpdate
也是不允许重复的。
而如 compoentDidMount
的生命周期,会依次调用 Mixins
,然后再调用组件中定义的 compoentDidMount
。
例如,上面的 PureRenderMixin
提供的对象中,有一个 shouldComponentUpdate
方法,即是将这个方法增加到了 MyDialog
上,此时 MyDialog
中不能再定义 shouldComponentUpdate
,否则会抛出异常。
//react-addons-pure-render-mixin 源码
var shallowEqual = require('fbjs/lib/shallowEqual');
module.exports = {
shouldComponentUpdate: function(nextProps, nextState) {
return (
!shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState)
);
},
};
Mixins 的缺点
Mixins
引入了隐式的依赖关系。例如,每个
mixin
依赖于其他的mixin
,那么修改其中一个就可能破坏另一个。Mixins
会导致名称冲突如果两个
mixin
中存在同名方法,就会抛出异常。另外,假设你引入了一个第三方的mixin
,该mixin
上的方法和你组件的方法名发生冲突,你就不得不对方法进行重命名。Mixins
会导致越来越复杂mixin
开始的时候是简单的,但是随着时间的推移,容易变得越来越复杂。例如,一个组件需要一些状态来跟踪鼠标悬停,为了保持逻辑的可重用性,将handleMouseEnter()
、handleMouseLeave()
和isHovering()
提取到HoverMixin()
中。然后其他人可能需要实现一个提示框,他们不想复制
HoverMixin()
的逻辑,于是他们创建了一个使用HoverMixin
的TooltipMixin
,TooltipMixin
在它的componentDidUpdate
中读取HoverMixin()
提供的isHovering()
来决定显示或隐藏提示框。几个月之后,有人想将提示框的方向设置为可配置的。为了避免代码重复,他们将
getTooltipOptions()
方法增加到了TooltipMixin
中。结果过了段时间,你需要在同一个组件中显示多个提示框,提示框不再是悬停时显示了,或者一些其他的功能,你需要解耦HoverMixin()
和TooltipMixin
。另外,如果很多组件使用了某个mixin
,mixin
中新增的功能都会被添加到所有组件中,事实上很多组件完全不需要这些新功能。渐渐地,封装的边界被侵蚀了,由于很难更改或移除现有的 mixin,它们变得越来越抽象,直到没有人理解它们是如何工作的。
React
官方认为在 React
代码库中,Mixin
是不必要的,也是有问题的。推荐开发者使用高阶组件来进行组件逻辑的复用。
2. HOC
React
官方文档对 HOC
进行了如下的定义:高阶组件(HOC
)是 React
中用于复用组件逻辑的一种高级技巧。HOC
自身不是 React
API 的一部分,它是一种基于 React
的组合特性而形成的设计模式。
简而言之,高阶组件就是一个函数,它接受一个组件为参数,返回一个新组件。
高阶组件形如下面这样:
//接受一个组件 WrappedComponent 作为参数,返回一个新组件 Proxy
function withXXX(WrappedComponent) {
return class Proxy extends React.Component {
render() {
return <WrappedComponent {...this.props}>
}
}
}
开发项目时,当你发现不同的组件有相似的逻辑,或者发现自己在写重复代码的时候,这时候就需要考虑组件复用的问题了。
这里我以一个实际开发的例子来说明,近期各大APP都在适配暗黑模式,而暗黑模式下的背景色、字体颜色等等和正常模式肯定是不一样的。那么就需要监听暗黑模式开启关闭事件,每个UI组件都需要根据当前的模式来设置样式。
每个组件都去监听事件变化来 setState
肯定是不可能的,因为会造成多次渲染。