浅说React生命周期

前言

这次想开一个新系列,能不能从比较浅的角度来说明白react到底在干嘛,而不是动不动就深入到里面说源码,当然研究源码本身没什么问题,但是对于新手来说确实有点困难,希望这个系列的文章能从浅的角度帮助入门react的工程师更好的了解react。

但是浅的角度并不是一无所知,有以下几个前提

  1. 必须写过react项目
  2. 熟悉react的常用api
  3. 有一定的js基础知识

前置知识

什么是jsx

JSX 是 JavaScript 语法的扩展,它用来声明 React 组件的 UI 结构。

  1. JSX 看起来类似 HTML ,但实际上完全是合法的 JavaScript 代码。
  2. JSX 会被编译器(如 Babel)编译为 React.createElement() 函数调用。
  3. JSX 支持嵌入 JavaScript 表达式,可以在 JSX 内直接使用 JavaScript 变量和函数。

JSX就是javascript用来描述React Virtual DOM的一种形式

jsx 复制代码
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

上面的代码会被babel转换成下面的形式

javascript 复制代码
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

createElement也就是所谓创建虚拟DOM

什么是虚拟DOM

虚拟dom,Virtual DOM 或者 Virtual Node

  1. 用JavaScript对象模拟真实DOM和DOM结构。
  2. React组件的render()方法或者函数组件执行,回一个虚拟DOM树,描述页面应该如何渲染。
  3. 在状态变更时,重新render()方法或者函数组件执行可以得到新的虚拟DOM树。
  4. React runtime会通过diff算法比较原始虚拟DOM和新的虚拟DOM树的差异。
  5. 最后只将变更的内容更新到真实的DOM中,避免不必要的操作, 这样通过虚拟DOM作为中间层,提高了对真实DOM的操作效率。
  6. 虚拟DOM让编写UI更加方便直观,不用直接操作 DOM。

正文

从两个方面了解生命周期

每次说到react生命周期,网上的文章都是列出一大堆生命周期api,看的我头大。 当然这个也没什么,为了让面试更顺利这api确实需要再面试之前记一下(本人是不喜欢这种方式),所以我会在后面才总结生命周期。 不过如果大家已经写过一到两年的前端,或者说本身就有丰富的开发经验(后端,移动端) 那么往小了看,也就是从组件的角度来看 组件生命周期无非就是三个

  • 挂载
  • 更新
  • 卸载

从大了来看 整个react运行机制上会有三个阶段

  • render
    • 调用组件的render方法,生成一个虚拟DOM。
    • 只在内存里生成虚拟DOM,不改变真实DOM。
  • pre-commit
    • 在这一步,先把更新前的虚拟DOM记下来,也就是记住更新之前的状态。
  • commit
    • 这一步才是把虚拟DOM画在网页上,也就是转换为真实DOM。
    • 还会先比较新旧虚拟DOM的不同,只更新需要改变的地方,不全部重新画。

react运行时的三个阶段,我们可以看做创建虚拟DOM,对虚拟DOM进行操作,将许虚拟DOM的更新到真实DOM上去。 而从组件的生命周期来看,就是一个组件的出生,成长,死亡。

但是这里要小心在表达的时候,render指的是里面的创建虚拟dom,还是说渲染实际内容到页面(也就是mount到页面上,mount到页面上的流程就包含了,render,pre-commit,commit)

hooks处于生命周期的什么位置

这里我并不想把任何hooks比作任何生命周期的钩子函数,而是直接讲解它是在什么时候执行。

这里也不会讲解hooks的具体用法,只是探讨一下hooks处于生命周期的什么阶段。

在react hooks大行其道的年代,react推崇函数组件,所以老版本利用生命周期钩子函数来写业务的年代也缓慢退出舞台。

不过即使是使用hooks和函数组件,我们难免会和当初的生命周期对应起来。

useEffect和useEffectLayout

比如经典中的经典。

什么时候请求我们的数据

什么时候来取消某个消息(请求,需要退出)的订阅

首先老生常谈的是

我们请求数据会放在useEffect里面,取消某个功能的订阅会放在useEffect返回函数之中。

useEffect返回函数不必多说,一定是组件卸载的时候才会执行,非常容易理解。

那么useEffect函数本身呢?也就是我们发送请求获取数据的时候。

useEffect的目的是为了执行有副作用的函数,比如异步获取数据,本身就会对ui造成影响,因为本来我们的页面就要根据服务器提供的数据进行渲染。

而react是纯函数为主的UI库,既UI = F(props(state))

我输入一样的props(state)要带来的结果是一样的。

但是我们又必须要异步获取数据,导致函数不是纯函数。

所以我们需要useEffect这个hooks来处理副作用的操作。

那么useEffect在哪个位置执行,既能保证纯函数也能保证副作用呢?

这里就比较巧妙了。

首先我们第一次加载页面的时候的确是纯函数,但是页面是没有数据渲染的只有结构的页面,没有服务器数据。

这个时候页面正在发送数据到服务器请求数据,数据来到浏览器。

useEffect里面重新设置state(其他组件的props),这个时候就相当于其他地方输入了新的state(props),页面重新渲染,获得想要的页面,当然这一个过程非常快,用户根本就没法感知到。

也就是useEffect是在react的组件渲染到真实页面上之后发生的。

就是为了保证纯函数这一个特点。

在react官网对于useEffect就有明确的执行时机useEffect -- React

明显是在页面渲染在浏览器之后

在回到前面的说明,useEffect执行是在commit阶段,也就是挂载,更新阶段commit的时候都会执行useEffect。

那和useEffect 类似的还有useLayoutEffect,也是在commit阶段执行。

但是useLayoutEffectuseEffect本身就有区别,这里我们不做探究,useLayoutEffect页面真实渲染之前执行,useEffect在页面渲染之后执行。

useEffect的clean函数也就是返回的函数什么时候执行。

如果发生了更新,那么就会运行一次clean函数,如果组件被卸载了,那么会运行最后一次。(useLayoutEffect类似)

自此我们学会了最经典的useEffect在生命周期的位置。

图像来自React class and hooks lifecycle explained (hello-js.com)

其他hooks

其他hooks,比如useState(useReducer),useMemo,useContext,useCallback这些常用的hooks又在什么位置呢?

我们从我们写代码的层面来分析。

比较明确的是这些明显都是和state状态有关

状态在react之中可以被更新的,这些hooks在更新阶段根据场景重新执行

比如useState会重新在setState更新之后获取最新的state,useMemo在监听的到依赖变化之后,返回最新的缓存值;useContext在某个state更新之后,引用context的组件获取最新值。

他们都是在内容被渲染到页面执行,当然我们的更新阶段,包含了render(重新创建虚拟dom),pre-commit(获取更新之前的状态)。

图来自 React hooks lifecycle diagram (wavez.github.io)

生命周期函数钩子

终于来到了钩子函数阶段。

关于钩子函数我们需要注意的其实就只有这两个,他们也是react 16新增的函数。

  • getDerivedStatefromProps
  • getsnapshotbeforeupdate

关于它们的位置非常好记,他们分别在shouldComponentUpdate之前(getDerivedStatefromProps),之后(getsnapshotbeforeupdate还在componentDidmount(componentdidupdate)之前)

这个两个玩意是干嘛的,不过我们先回顾一下其他的钩子,16和16之前依然存在的(被去除的后面有)

  • shouldComponentUpdate
  • componentDidmount
  • componentdidupdate
  • componentWillUnmount

通过名字我们就知道他们在哪个周期。

比如

  • componentDidmount,接收prevProps, prevState, 在页面内容出现在页面之后,我们一般用来请求数据。
  • shouldComponentUpdate,接收nextProps,nextState,返回boolean值,决定组件是否重新渲染。

新的两个周期

  • getSnapshotBeforeUpdate

    接收prevProps,prevState, 返回snapshot的值, 组件的虚拟DOM已经重新渲染完成(在shouldComponentUpdate之后),但实际DOM还未开始更新,这里可以获取未更新的dom的值来生成一个snapshot,返回出去的snapshot作为componentDidmount的参数

  • getDerivedStateFromProps:

    • 会在调用 render 方法之前被调用,在挂载和更新时都会被调用。也就是在shouldComponentUpdate之前
    • 它接收到最新的 props 和当前的 state 作为参数。
    • 需要返回一个对象来更新 state,或者返回 null 来表示 state 不需要更新。
    • 通过将 props 映射到 state,可以在 props 改变时更新 state。

旧的,不推荐使用三个周期

  • UNSAFE_componentWillReceiveProps(nextProps, nextContext)

    • 被 getDerivedStateFromProps 替代
    • 它会在已挂载的组件接收到新的 props 之后被调用。
    • 可以通过对比 nextProps 和 prevProps 来执行状态更新。
    • 典型用法是根据 props 改变来更新 state。
  • UNSAFE_componentWillMount() componentWillMount非常简单,就是在组件挂载之前钩子函数

  • UNSAFE_componentWillUpdate(nextProps, nextState) componentWillUpdate会在组件接收到新的props或state后立即被调用。

为什么这三个周期会被取消,就和最新的fiber架构有关。

这里简单来解释就是fiber可以看成一个一个的节点,每一个渲染的任务就是一个fiber节点,react运行时和根据浏览器空闲的时间以及任务的优先级来执行任务。

但是这个任务是可以被中断的,比如一个任务执行到一半,就退出了。下次回来的要重新开始执行这个任务,这三个周期会被重复执行。

至于为什么这个几个生命周期会被重复执行,其他的不影响,后面新文章再说。

相关推荐
煸橙干儿~~2 分钟前
分析JS Crash(进程崩溃)
java·前端·javascript
安冬的码畜日常12 分钟前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n039 分钟前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
昨天;明天。今天。1 小时前
案例-任务清单
前端·javascript·css
zqx_72 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己2 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称3 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色3 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2343 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河3 小时前
CSS总结
前端·css