前言
最近在项目中遇到react的组件多次渲染的问题,最后虽然顺利解决了但也同时发现了自己对于react生命周期的不熟悉,于是便找出react的文档重新去了解下,重新学习之后总结为以下两个主要知识点:react组件的整个渲染流程 react组件更新的注意点。
生命周期流程
由上图可以了解到react组件的生命周期大致可以分为三个阶段:
- 挂载(初始化)阶段
- 更新(运行中)阶段
- 卸载阶段
DOM挂载阶段
- defaultProps:给父组件传递过来的props赋初期值
Demo.defaultProps={ demo:"test" }
- constructor:数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。
1.只要使用了constructor()就必须写super(),否则会导致this指向错误。 2.在constructor生命周期中可以直接设置state的值。 constructor(props) { super(props); //只有此种状态可以直接设置state this.state = {}; }
componentWillMount:用于挂在之前,在组件的整个生命周期内只执行一次,此时可以进行setState- componentDidMount:用于组件在DOM成功渲染之后执行,此生命周期中可以获取真实的dom元素,在组件的整个生命周期内只执行一次,另外官方文档建议在这个生命周期适合进行异步请求
import axios from 'axios' componentDidMount(){ document.querySelector("#root") //异步请求 aixos.get('/demo') .then(res=>{}) }
为什么官方建议在componentDidMount生命周期中进行异步请求?
正常点逻辑是在越早的生命周期进行请求越好,例如vue中建议在created(beforeCreated时data还没挂载)生命周期中进行异步请求,而在react中componentDidMount之前还有componentWillMount和constructor,为何不再这两个生命周期中调用呢,首先我们进行的异步请求一般都会将获取的数据渲染到组件上及调用setState,我们下面以这个为基础来分析。
- constructor中为何不建议进行异步请求,个人认为原因有两点:1.不符合我们日常的编码规范 2.此处进行setState无意义。
- componentWillMount将会在16.x慢慢被废弃掉,所以不建议使用
- 在此生命周期中进行异步请求因为此生命周期调用setState会进行render渲染
综上所述还是在componentDidMount中进行异步请求比较合理。
组件挂载中是如何将虚拟DOM转化成真实DOM的?
在组件挂载之前React组件是一个JavaScript对象也就是我们所说的虚拟DOM。React以二叉树的形式表示整个UI结构的方式创建自己的虚拟DOM。它将虚拟DOM树保存到内存中。结构如下
//react组件中html <div class="demo"> <p class="item">ui components</p> </div>
//JavaScript表示都数据结构 { tagName: 'div', // 节点标签名 props: { // DOM的属性,用一个对象存储键值对 class: 'demo' }, children:[ {tagName:"p",props:{class:"item"}} ] }
虚拟DOM vs 直接操作真实DOM?
在正常的前端页面中,如果是整个页面数据都发生变化,此时我们通过重置整个html是合理都,但是如果仅仅是页面某一快数据发生变化,那我们此时重置整个页面都操作就时分浪费性能了。对于虚拟DOM而言只用更新对象上相关数据后对页面都部分进行重新渲染即可。比较如下:
- 原生DOM: render html string + 重新创建所有 DOM 元素
- Virtual DOM: render Virtual DOM + diff + 必要的 DOM 更新
DOM更新
当我们使用setState或者父组件的props改变时候会触发组件的更新,此时生命周期如下:
componentWillReceiveProps(方法即将过时,不在过多介绍)- shouldComponentUpdate:参数如下nextProps(即将更新的props), nextState(即将更新的state) 注意:如果 返回值为 false,则不会调用 componentDidUpdate()。
shouldComponentUpdate(nextProps,nextState){ console.log('----should update---') return false } componentDidUpdate(){ console.log('-----did update------') }
componentWillUpdate(方法即将过时,不在过多介绍)- componentDidUpdate :组件更新时会触发次钩子函数。例如:setState或父元素的props改变。
- render
1.在react组件中同事多次执行setState最终只会render一次 2.方法是 class 组件中唯一必须实现的方法 3.当被调用时,返回以下类型之一:React元素 数组或fragments 字符串或数值类型(被渲染成文本节点) 布尔类型或 null(不渲染) 4.shouldComponentUpdate() 返回 false,则不会调用 render() 5.render为纯函数,不修改组件 state 的情况下每次调用都返回相同的值,并且不会与浏览器交互。
为何在react不能直接改变state?
直接使用this.state是能够改变state的值的但是并不会触发组件的更新,这样会导致两个问题:组件页面的内容和组件的状态不一致 组件内的状态不可控。所以我们在进行状态更新时候需要调用setState方法来进行。
为何直接给state赋值不起作用呢?
根据上一个问题我们知道了setState都执行步骤,如何我们直接给状态赋值是不会将状态放入state中的。因此就不会触发react的更新。
setState是同步的还是异步的呢?
- setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
- setState本身的代码是同步的,知识因为react的合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值
组件更新原理
在react中的组件加载中,更新操作是组件不可避免的操作,在以上内容中我们已经大概了解了setState的执行步骤,本节主要来了解过在更新过程中react是通过什么方式实现其复杂的组件新。
为何setState触发组件更新可能是异步对?
执行步骤:
- 在函数中调用this.setState({demo:"demo"})
- 此时奖setState中的新状态存入pending队列中
- 判断状态是否需要批量更新
- 如果是则等所有带新状态都保存在pending在更新(updateComponent)
- 如果不是则开始更新新状态
以上是我们在调用setState时候的执行步骤,下面我们来了解下react调用setState是如何触发组件更新的:
- setState挂载到组件原型链上,
ReactComponent.prototype.setState = function (partialState, callback) { !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? "development" !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0; this.updater.enqueueSetState(this, partialState); if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); } };
- 执行setState时调用enqueueSetState
什么情况下setState是同步的呢?
在我们使用React封装的事件时会进入一个事务,这是不会直接触发batch update更新 。 而当我们使用原生的事件机制时(比如 addEventListener ),由于缺少了React的封装,会使得 setState 直接触发 batch update更新,从而同步更新state。
DOM销毁
- componentWillUnmount:组件销毁时触发的生命周期。
在react生命周期中componentWillUnmount虽然我们很少在此处进行操作,但是在某些场景下可以发挥很大到作用比如在离开组件时候结束一个定时器或是解绑一个全局事件。
总结
通过上文的总结,我对react对组件加载有了更深刻对认识,其过程大概可分为三大阶段:挂载,更新,卸载。对于react组件的加载我认为以下几点是需要我们关注的:挂载阶段的虚拟Dom是如何渲染成真实DOM的,更新时候setState做了什么。