进一步认识Hooks:如何正确理解函数组件的生命周期

一、class组件生命周期

  基于class的组件作为react诞生之初就存在的机制,他的语法早已深入人心,甚至至今为止react的官方文档中仍然是以class组件为基础,而函数组件和hooks则是做为新的特性补充说明。

这其实有两个原因:

  一是react团队尽最大努力保持api的稳定行,不希望给你造成一个class组件将被废弃的感觉;

  二是大量的存量应用其实都是用class组件实现的,无论是对于维护者还是加入者来说,了解class组件都是很有必要的。

  Class 组件和函数组件是两种实现 React 应用的方式,虽然它们是等价的,但是开发的思想有很大不同。如果你是从 Class 组件转换到 Hooks 的方式,那么很重要的一点就是,你要学会忘掉 Class 组件中的生命周期概念,千万不要将原来习惯的 Class 组件开发方式映射到函数组。比如如何在函数组件中实现 componentDidMount, componentDidUpdate 这样的 Class 组件才有的生命周期方法,你应该通过理解 Hooks 的方式去思考业务需求应该如何实现。

  为了理解函数组件的执行过程,我们不妨思考一下react的本质:从model到view的映射。假设状态永远不变,那么实际函数组件就相当于一个模板引擎之执行一次,但是react本身正式为动态的状态变化而设计的,而可能引起状态不变化的原因基本只有两个:

  1.用户操作产生的事件,比如点击了某个按钮。2.副作用产生的事件,比如发起某个请求正确返回了。

  这两种事件本身并不会导致组件的重新渲染,但是我们在这两种事件处理函数中,一定是因为改变了某个状态,这个状态可能是state或者context,从而导致了ui的重新渲染。

  对于第一种情况,其实函数组件和class组件思路完全一样。通过事件处理函数来改变某一个状态;对于第二种情况,在函数组件中式通过useEffect这个hook更加直观和语义化和方式来描述。对应到class组件,则是通过手动判断props或者state的变化来执行的 。

  在函数组件中你要思考的方式永远式:当某个状态发生变化时,我要做什么,而不再是在class组件中的某个生命周期方法中我要作什么。

  重新思考组件的生命周期

在传统的类组件中,有专门定义的生命周期方法用于执行不同的逻辑,那么它们在函数组件的存在的形式又是什么样的呢?接下来我就带你一起看看在函数组件中,是如何思考组件的生命周期的。

  构造函数

在类组件中有一个专门的方法叫做constructor,也就是构造函数,在里面我们会做一些初始化的事情,比如state的初始状态,或者定义一些类的实例成员。而函数组件里 之有一个函数,没有所谓的对象。

所以你在日常开发中,是无需去将功能映射到传统的生命周期的构造函数的概念,而是要从函数的角度出发,去思考功能如何去实现。比如在这个例子中,我们需要的其实就是抓住某段代码只需要执行一次这样一个本质的需求,从而能够更自然地用 Hooks 解决问题。

  三种常用的声明周期方法

在类组件中,componentDidMount,componentWillUnmount,和componentDidUpdate这三个生命周期方法可以说是日常开发最常用的。业务逻辑通常要分散到不同的生命周期方法中,

  而函数组件中,这几个声明周期方法可以统一到useEffect这个Hook,正如useEffect的字面含义,他的作用就是触发一个副作用,即在组件每次render之后去执行。

在第三讲中其实你已经看到useEffect的方法,下面的代码演示了这三个生命周期方法是如何用useEffect实现的:

useEffect(()=>{
    //componentDidMount+componentDidUpdate
consoloe.log('这里基本上等价于componentDidMount+componentDidUpdate');
return ()=>{
  //componentWillUnmount
console.log('这里基本等价于componentWillUnmount')
}
},[deps])

  一方面,useEffect(callback)这个hook接收的callback,只有当依赖项变化时才被执行。而传统的componentDidUpdate则是一定会执行。这样开来,hook的机制其实更具有语义化,因为过去在componentDidUpdata中,我们通常都需要手动判断这个状态是否发生状态变化,然后再执行特定的逻辑。

  另一方面,callBack返回的函数(一般用于清理工作),在下一次依赖项发生变化以及组件销毁之前执行,而传统的comPonentWillUnmont之在组件销毁时才会执行。

useEffect 接收的返回值是一个回调函数,这个回调函数不只是会在组件销毁时执行,而且是每次 Effect 重新执行之前都会执行,用于清理上一次 Effect 的执行结果。理解这一点非常重要。useEffect 中返回的回调函数,只是清理当前执行的 Effect 本身。这其实是更加语义化的,因此你不用将其映射到 componentWillUnmount,它也完全不等价于 componentWillUnmount。你只需记住它的作用就是用于清理上一次 Effect 的结果就行了,这样在实际的开发中才能够使用得更加自然和合理。

  其他声明周期方法

刚才我列举了几个 Class 组件中最为常用的生命周期方法,并介绍了对于同样的需求,在函数组件中应该如何去用 Hooks 的机制重新思考它们的实现。这已经能覆盖绝大多数的应用场景了。但是 Class 组件中还有其它一些比较少用的方法,比如 getSnapshotBeforeUpdate, componentDidCatch, getDerivedStateFromError。比较遗憾的是目前 Hooks 还没法实现这些功能。因此如果必须用到,你的组件仍然需要用类组件去实现。

  已有应用是否因该迁移到Hooks?

说了这么多,你可能会觉得写 React 应用就一定非 Hooks 不可了,其实也并非绝对。比如说很多时候,你面临的可能并不是开始一个全新的项目,而是参与到一个已有的项目中。那么就很可能会遇到这样一个问题:对于已有项目中的 Class 组件,是否要重构到函数组件和 Hooks 呢?

  答案其实很明确:完全没必要。

在 React 中,Class 组件和函数组件是完全可以共存的。对于新的功能,我会更推荐使用函数组件。而对于已有的功能,则维持现状就可以。除非要进行大的功能改变,可以顺便把相关的类组件进行重构,否则是没有必要进行迁移的。因为终究来说,能正确工作的代码就是好代码。React 组件的两种写法本身就可以很好地一起工作了:

  1.类组件和函数组件可以相互引用

  2.Hooks很容易就能转换成高阶组件,并提过类组件使用

  小结:

函数组件和类组件在思考方式上的区别,虽然 Hooks 在功能上基本可以映射到传统的 Class 组件的生命周期方法,但是它们却又不是完全等价的。在实现具体的业务功能的时候,都应该尽量从 Hooks 的语义角度出发去思考组件是如何展现和交互的,这样才能更加顺滑地切换到函数组件的开发方式。思考题

posted @ 2022-02-21 12:00  前端乔  阅读(782)  评论(0编辑  收藏  举报