我们知道组件和自顶向下的单向数据流帮我们将大型 UI 组织成小的、独立的、可复用的部分。然而,由于逻辑是有状态的,不能提取到函数或其他组件,我们通常无法进一步分解复杂组件。
这些情况非常常见,包括动画、表单处理、异步请求数据等,以及我们希望从组件中完成的许多其他事情。 当我们试图单独使用组件来解决这些用例时,我们通常会得到:
- 难以重构和测试的大型组件(我们已经制造了很多这种组件)
- 不同组件和生命周期方法之间的重复逻辑(各种场景都很常见)
- 发明了很多复杂的模式,比如 Mixins、 Render porp 和 高阶组件(HOC)
Mixins
主要应用在ES6普及之前,在还使用React.createClass时是非常常见的一种方式,例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25var LogMixin = {
log: function() {
console.log('log');
},
componentDidMount: function() {
console.log('in');
},
componentWillUnmount: function() {
console.log('out');
}
};
var User = React.createClass({
mixins: [LogMixin],
render: function() {
return (<div>...</div>)
}
});
var Goods = React.createClass({
mixins: [LogMixin],
render: function() {
return (<div>...</div>)
}
});
通过 Mixins,User 和 Goods 都实现了逻辑复用
- ES6 class,规范不支持 mixins。
- 不够直接,mixins 改变了 state,因此也就很难知道一些 state 是从哪里来的,尤其是当不止存在一个 mixins 时,可能还会相互依赖,相互耦合,及不利于代码维护。
- 命名冲突,不同的 mixins 中的方法可能会相互冲突、相互覆盖。
HOC
高阶组件是参数为组件,返回值为新组件的函数。
因为Mixin带来的危害比他产生的价值还要大,所以React全面推荐使用高阶组件来替代它。
1 | // 此函数接收一个组件... |
但是,是否解决了在使用 mixin 时遇到的问题?
- ES6 class,这里不再是问题了,ES6 class 创建的组件能够和 HOC 结合。
- 不够直接,在 mixin 中,我们不知道 state 从何而来,在 HOC 中,我们不知道 props 从何而来。
- 命名冲突,我们仍然会面临该问题。两个使用了同名 prop 的 HOC 将遭遇冲突并且彼此覆盖,并且这次问题会更加隐晦,因为 React 不会在 prop 重名是发出警告。
而且引入了更多的规则:
- 不能在 render 方法中使用 HOC
- 务必复制静态方法
- Refs 不会被传递
- 理解难度上升,开发者需要“守规矩”(不修改传入组件的原型、透传 props 等)
高阶组件主要解决的问题是代码复用,但没有解决状态的不明确性以及命名冲突,很简单的一个例子就是封装input组件的时候,需要时刻注意value不被重置。
Render Props
由于HOC也存在一定的复杂性,社区又探索出一种新的模式:
在 React 组件之间使用一个值为函数的 prop 来共享代码
具体实现类似:
1 | // 一个类型为函数的prop |
虽然解决了 HOC 和 Mixins 中来源不清的 state、props以及命名冲突。但是很不直观,比较反直觉,而且引入了不必要的嵌套。
并且,当我们的使用者的组件为一个React.PureComponent的时候,由于浅比较 props 的时候总是false,所以会发生不可预料的后果,这就限制了使用者定义的组件。
Hooks
可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hooks将 React 哲学(显式数据流和组合)应用于组件内部,而不仅仅是组件之间。
与Render props 或高阶组件等模式不同,hook不会在组件树中引入不必要的嵌套。 也就没有mixins等的缺点。
假设我们设计一个监听当前窗口宽度的组件(例如,在一个可变视区上显示它的宽度)。
现在有几种方法可以编写这种代码,包括编写一个类,设置一些生命周期方法,或者想在组件之间重用它,甚至可以抽象一个 render prop 或一个高阶组件。但我认为这样会更好:
1 | function MyResponsiveComponent() { |
甚至,自定义的hook我们可以实现为这样:
1 | function useWindowWidth() { |
Hooks实现的伪代码
1 | // by @jamiebuilds |
由于 Hooks 在每次渲染时的顺序都是相同的,因此我们可以为每次调用提供正确的组件状态。
React 把 Hooks 的状态保持在 React 保存类状态的同一个地方。 React 有一个内部更新队列,它是任何状态的真实来源,不论如何定义组件。
由于功能逻辑等的拆离,会让业务组件变得更好维护,下图可以直观感受下:
由此,我们只需要实现较小粒度逻辑的hooks,然后在组件中组合它们就可以完成复杂的交互逻辑。