一、React-Hooks 设计动机初探

首先得知道,什么是类组件、什么是函数组件。

1. 何谓类组件

所谓类组件,就是基于 ES6 Class 这种写法,通过继承 React.Component 得来的 React 组件。

对于类组件的执行,源码是在react-reconciler/src/ReactFiberClassComponent.js中:

  1. function constructClassInstance(
  2. workInProgress, // 当前正在工作的 fiber 对象
  3. ctor, // 我们的类组件
  4. props // props
  5. ){
  6. /* 实例化组件,得到组件实例 instance */
  7. const instance = new ctor(props, context)
  8. }

在 class 组件中,除了继承 React.Component ,底层还加入了 updater 对象,组件中调用的 setState 和 forceUpdate 本质上是调用了 updater 对象上的 enqueueSetState 和 enqueueForceUpdate 方法。

源码位置:react/src/ReactBaseClasses.js

  1. function Component(props, context, updater) {
  2. this.props = props; //绑定props
  3. this.context = context; //绑定context
  4. this.refs = emptyObject; //绑定ref
  5. this.updater = updater || ReactNoopUpdateQueue; //上面所属的updater 对象
  6. }
  7. /* 绑定setState 方法 */
  8. Component.prototype.setState = function(partialState, callback) {
  9. this.updater.enqueueSetState(this, partialState, callback, 'setState');
  10. }
  11. /* 绑定forceupdate 方法 */
  12. Component.prototype.forceUpdate = function(callback) {
  13. this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
  14. }

如上可以看出 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中:

  1. function renderWithHooks(
  2. current, // 当前函数组件对应的 `fiber`, 初始化
  3. workInProgress, // 当前正在工作的 fiber 对象
  4. Component, // 我们函数组件
  5. props, // 函数组件第一个参数 props
  6. secondArg, // 函数组件其他参数
  7. nextRenderExpirationTime, //下次渲染过期时间
  8. ){
  9. /* 执行我们的函数组件,得到 return 返回的 React.element对象 */
  10. let children = Component(props, secondArg);
  11. }

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 在使用层面有着严格的规则约束
  • 函数组件给了我们一定程度的自由,却也对开发者的水平提出了更高的要求。