一、React-Hooks 设计动机初探
首先得知道,什么是类组件、什么是函数组件。
1. 何谓类组件
所谓类组件,就是基于 ES6 Class 这种写法,通过继承 React.Component 得来的 React 组件。
对于类组件的执行,源码是在react-reconciler/src/ReactFiberClassComponent.js中:
function constructClassInstance(
workInProgress, // 当前正在工作的 fiber 对象
ctor, // 我们的类组件
props // props
){
/* 实例化组件,得到组件实例 instance */
const instance = new ctor(props, context)
}
在 class 组件中,除了继承 React.Component ,底层还加入了 updater 对象,组件中调用的 setState 和 forceUpdate 本质上是调用了 updater 对象上的 enqueueSetState 和 enqueueForceUpdate 方法。
源码位置:react/src/ReactBaseClasses.js
function Component(props, context, updater) {
this.props = props; //绑定props
this.context = context; //绑定context
this.refs = emptyObject; //绑定ref
this.updater = updater || ReactNoopUpdateQueue; //上面所属的updater 对象
}
/* 绑定setState 方法 */
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
}
/* 绑定forceupdate 方法 */
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
}
如上可以看出 Component 底层 React 的处理逻辑是类组件执行构造函数过程中会在实例上绑定 props 和 context ,初始化置空 refs 属性,原型链上绑定setState、forceUpdate 方法。对于 updater,React 在实例化类组件之后会单独绑定 update 对象。
问题:如果没有在 constructor 的 super 函数中传递 props,那么接下来 constructor 执行上下文中就获取不到 props ,这是为什么呢?
答:绑定 props 是在父类 Component 构造函数中,执行 super 等于执行 Component 函数,此时 props 没有作为第一个参数传给 super() ,在 Component 中就会找不到 props 参数,从而变成 undefined 。
2. 何谓函数组件
函数组件顾名思义,就是以函数的形态存在的 React 组件。函数组件内部无法定义和维护 state,因此它还有一个别名叫“无状态组件”。
对于函数组件的执行,源码是在react-reconciler/src/ReactFiberHooks.js中:
function renderWithHooks(
current, // 当前函数组件对应的 `fiber`, 初始化
workInProgress, // 当前正在工作的 fiber 对象
Component, // 我们函数组件
props, // 函数组件第一个参数 props
secondArg, // 函数组件其他参数
nextRenderExpirationTime, //下次渲染过期时间
){
/* 执行我们的函数组件,得到 return 返回的 React.element对象 */
let children = Component(props, secondArg);
}
3. 对比(不同)
- 类组件需要继承 class,函数组件不需要;
- 类组件可以访问生命周期方法,函数组件不能;
- 类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,而函数组件不可以;
- 类组件中可以定义并维护 state(状态),而函数组件不可以;
在 React-Hooks 出现之前的世界里,类组件的能力边界明显强于函数组件,但要进一步推导“类组件强于函数组件”,未免显得有些牵强。
函数组件和类组件本质的区别是什么呢?
答:对于类组件来说,底层只需要实例化一次,实例中保存了组件的 state 等状态。对于每一次更新只需要调用 render 方法以及对应的生命周期就可以了。但是在函数组件中,每一次更新都是一次新的函数执行,一次函数组件的更新,里面的变量会重新声明。
4. 重新理解类组件
React 类组件内部预置了相当多的“现成的东西”等着你去调度/定制,state 和生命周期就是这些“现成东西”中的典型。你只需要轻轻地继承一个 React.Component 即可。类组件固然强大, 但它绝非万能。
5. 深入理解函数组件
相比于类组件,函数组件肉眼可见的特质自然包括轻量、灵活、易于组织和维护、较低的学习成本等,函数组件会捕获 render 内部的状态,这是两类组件最大的不同。
函数组件真正地把数据和渲染绑定到了一起。
React 组件本身的定位就是函数,一个吃进数据、吐出 UI 的函数。
React 框架的主要工作,就是及时地把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去。
React 的数据应该总是紧紧地和渲染绑定在一起的,而类组件做不到这一点。
二、Hooks 的本质:一套能够使函数组件更强大、更灵活的“钩子”
什么是hook?
它可以让你在不编写class的情况下使用state以及react的其他特性。
函数组件比起类组件“少”了很多东西,比如生命周期、对 state 的管理等。这就给函数组件的使用带来了非常多的局限性。hooks的出现允许你自由地选择和使用你需要的那些能力。
1. useState():为函数组件引入状态
- 同样逻辑的函数组件相比类组件而言,复杂度要低得多得多。
- 状态和修改状态的 API 名都是可以自定义的。
- 它就像类组件中 state 对象的某一个属性一样,对应着一个单独的状态,允许你存储任意类型的值。
2. useEffect():允许函数组件执行副作用操作
- useEffect 在一定程度上弥补了生命周期的缺席。
- 仅在挂载阶段执行一次的副作用:传入回调函数,且这个函数的返回值不是一个函数,同时传入一个空数组。
- 仅在挂载阶段和卸载阶段执行的副作用:传入回调函数,且这个函数的返回值是一个函数,同时传入一个空数组。
- useEffect 回调中返回的函数被称为“清除函数”,这个规律不会受第二个参数或者其他因素的影响,只要你在 useEffect 回调中返回了一个函数,它就会被作为清除函数来处理。
三、Hooks 是如何帮助我们升级工作模式的
- 告别难以理解的 Class;
- 解决业务逻辑难以拆分的问题;
- 使状态逻辑复用变得简单可行;
- 函数组件从设计思想上来看,更加契合 React 的理念。
1. 告别难以理解的 Class:把握 Class 的两大“痛点”
主要痛点在于this和生命周期。
2. Hooks 如何实现更好的逻辑拆分
主要解决逻辑曾经一度与生命周期耦合在一起。
Hooks 能够帮助我们实现业务逻辑的聚合,避免复杂的组件和冗余的代码。
3. 状态复用:Hooks 将复杂的问题变简单
可以通过自定义 Hook,达到既不破坏组件结构、又能够实现逻辑复用的效果。
四、Hooks 并非万能
- Hooks 暂时还不能完全地为函数组件补齐类组件的能力:比如 getSnapshotBeforeUpdate、componentDidCatch 这些生命周期,目前都还是强依赖类组件的。
- Hooks 在使用层面有着严格的规则约束
- 函数组件给了我们一定程度的自由,却也对开发者的水平提出了更高的要求。