动画 + 大白话讲清楚React渲染原理

本文以大白话讲解React的渲染原理,从组件生命周期、React元素、虚拟DOM、首次渲染和更新过程等方面展开,深入浅出地介绍了React如何将JSX转换为真实DOM并进行高效更新。特别强调了生命周期中的关键方法、性能优化策略以及diff算法的应用,帮助读者理解React渲染背后的逻辑。
摘要由CSDN通过智能技术生成

前言

相信很多人跟我之前一样,看到源码两个字觉得触不可及,觉得离自己还很遥远,是需要非常多年的工作经验的大佬才能触及到的领域。就在去年我改变了这个想法,当时被react的几个生命周期执行顺序弄的睡不着觉,为什么有些时候生命周期的执行事与愿违?又为什么数组中必须要加上key属性?为啥在render中不能写setState等等问题…在一系列的问题中,我终于还是打开了那份久违的源码,并且Ctrl + F慢慢探索了起来。

直到今天,趁着二季度业务结束忙里偷闲总结出这份不看源码也能让你看懂的渲染原理。因为有些地方需要承上启下,所以文本分为两大部分讲解,一部分是首次挂载渲染原理,另一部分是更新和卸载原理,很多地方非常抽象,希望大家仔细阅读,不然容易脱节。废话不多话,开车!!

正文

在开始之前,需要一些前置知识才能帮助我们更好的理解整个渲染过程。首先就是生命周期(16版本之后),为什么要讲一下生命周期?跟渲染原理有关系吗?当然有,如果你不理解渲染原理的话,更新一个嵌套很深的组件你甚至连父与子生命周期执行的先后顺序都不知道。本文直接对照16版本之后的新生命周期进行讲解,就不讲解老版本了。

初探-生命周期

顾名思义,跟人生一样,生命周期就是一个组件从诞生销毁的过程。React在组件的生命周期中注册了一系列的钩子函数,支持开发者在其中注入代码,并在适当的时机运行。这里指的生命周期仅针对于类组件中的钩子函数。因为生命周期不是本文的重点,所以Hooks中的新增的钩子函数在本文中均不涉及,可以以后出个Hooks原理篇。

从图中可以看到,我把生命周期分为了挂载阶段更新阶段卸载阶段三个阶段。同时,在挂载阶段更新阶段都会运行getDerivedStateFromPropsrender,卸载阶段很好理解,只有一个componentWillUnMount,在卸载组件之前做一些事情,通常用来清除定时器等副作用操作。那么挂载阶段更新阶段中的生命周期我们来逐一看下每个运行点及作用。

1. constructor

在同一个类组件对象只会运行一次。所以经常来做一些初始化的操作。同一个组件对象被多次创建,它们的construcotr互不干扰。

注意:在construcotr中要尽量避免(最好禁止)使用setState 我们都知道使用setState会造成页面的重新渲染,但是在初始化阶段,页面都还没有将真实DOM挂载到页面上,那么重新渲染的又有什么意义呢。除异步的情况,比如setInterval中使用setState是没问题的,因为在执行的时候页面早已渲染完成。但也最好不要,容易一些引起奇怪的问题。

 constructor(props) {super(props);this.state = {num: 1};//不可以,直接Warningthis.setState({num: this.state.num + 1});//可以使用,但不建议setInterval(()=>{this.setState({num: this.state.num + 1});}, 1000);} 

2. 静态属性 static getDerivedStateFromProps

该方法是一个静态属性,在16版本之前不存在,在新版生命周期中主要用来取代componentWillMountcomponentWillReceiveProps,因为这两个老生命周期方法在一些开发者不规范的使用下极容易产生一些反模式的bug。因为是静态方法,所以你在其中根本拿不到this,更不可能调用setState

该方法在挂载阶段更新阶段都会运行。它有两个参数propsstate当前的属性值状态。它的返回值会合并掉当前的状态(state)。 如果返回了非Object的值,那么它啥都不会做,如果返回的是Object,那么它将会跟当前的状态合并,可以理解为Object.assign。通常情况下,几乎不怎么使用该方法。

 /** * 静态方法,首次挂载和更新渲染都会运行该方法 * @param {*} props 当前属性 * @param {*} state 当前状态 */static getDerivedStateFromProps(props, state){// return 1; //没用return {num: 999, //合并到当前state对象};} 

3. render

最重要的生命周期,没有之一。用来生成虚拟节点(vDom)树。该方法只要遇到需要重新渲染都会运行。同样的,在render中也严禁使用setState,因为会导致无限递归重新渲染导致爆栈

 render() {//严禁使用!!!this.setState({num: 1})return (<>{this.state.num}</>)} 

4. componentDidMount

该方法只会运行一次,在首次渲染时页面将真实DOM挂载完毕之后运行。通常在这里做一些异步操作,比如开启定时器、发起网络请求、获取真实DOM等。在该方法中,可以大胆使用setState,因为页面已经渲染完成。执行完该钩子函数后,组件正式进入到活跃状态。

 componentDidMount(){// 初始化或异步代码...this.setState({});setInterval(()=>{});document.querySelectorAll("div");} 

5. 性能优化 shouldComponentUpdate

在原理图更新阶段中可以看到,执行完static getDerivedStateFromProps后,会执行该钩子函数。该方法通常用来做性能优化。它的返回值(boolean)决定了是否要进行渲染更新。该方法有两个参数nextPropsnextState表示此次更新(下一次)的属性状态。通常我们会将当前值与此次要更新的值做比较来决定是否要进行重新渲染。

React中,官方给我们实现好了一个基础版的优化组件PureComponent,就是一个HOC高阶组件,内部实现就是帮我们用shouldComponentUpdate做了浅比较优化。如果安装了React代码提示的插件,我们可以直接使用rpc + tab键来生成模版。注意:继承了PureComponent后不需要再使用shouldComponentUpdate进行优化。

 /** * 决定是否要进行重新渲染 * @param {*} nextProps 此次更新的属性 * @param {*} nextState 此次更新的状态 * @returns {boolean} */shouldComponentUpdate(nextProps, nextState){// 伪代码,如果当前的值和下一次的值相等,那么就没有更新渲染的必要了if(this.props === nextProps && this.state === nextState){return false;}return true;} 

6. getSnapshotBeforeUpdate

如果shouldComponentUpdate返回是true,那么就会运行render重新生成虚拟DOM树来进行对比更新,该方法运行在render后,表示真实DOM已经构建完成,但还没有渲染到页面中。可以理解为更新前的快照,通常用来做一些附加的DOM操作。

比如我突然想针对具有某个class的真实元素做一些事情。那么就可以在此方法中获取元素并修改。该函数有两个参数prevPropsprevState表示此次更新前的属性状态,该函数的返回值(snapshot)会作为componentDidUpdate的第三个参数。

 /** * 获取更新前的快照,通常用来做一些附加的DOM操作 * @param {*} prevProps 更新前的属性 * @param {*} prevState 更新前的状态 */getSnapshotBeforeUpdate(prevProps, prevState){// 获取真实DOM在渲染到页面前做一些附加操作...document.querySelectorAll("div").forEach(it=>it.innerHTML = "123");return "componentDidUpdate的第三个参数";} 

7. componentDidUpdate

该方法是更新阶段最后运行的钩子函数,跟getSnapshotBeforeUpdate不同的是,它的运行时间点是在真实DOM挂载到页面后。通常也会使用该方

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值