React 组件生命周期

  • React 组件生命周期已关闭评论
  • 85 次浏览
  • A+
所属分类:Web前端
摘要

求上进的人,不要总想着靠谁,人都是自私的,自己才是最靠得住的人。React 中生命周期划时代几个节点,React 16.2 之前处于老的生命周期,之后提出了新的生命周期。而函数式组件在 React 16.8 之前是没有状态和生命周期的,在 React 16.8 版本通过引入 Hooks 使得函数式组件也能有状态和生命周期了。


求上进的人,不要总想着靠谁,人都是自私的,自己才是最靠得住的人。

React 中生命周期划时代几个节点,React 16.2 之前处于老的生命周期,之后提出了新的生命周期。而函数式组件在 React 16.8 之前是没有状态和生命周期的,在 React 16.8 版本通过引入 Hooks 使得函数式组件也能有状态和生命周期了。

1. 初始化阶段

1.1 componentWillMount:

组件即将挂载,初始化数据作用,即 render 之前最后一次修改状态的机会。

// 组件即将挂载 componentWillMount() {   // 初始化数据作用   console.log("componentWillMount") }  /* 在 16.2 之后版本使用会出现以下警告 ⚠️⚠️⚠️   react-dom.development.js:86 Warning: componentWillMount has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.    * Move code with side effects to componentDidMount, and set initial state in the constructor.   * Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.    Please update the following components: App   */  // 组件即将挂载 - 强制去掉警告,UNSAFE 提示开发者这是一个不安全的生命周期方法。 UNSAFE_componentWillMount() {   // 初始化数据作用   console.log("componentWillMount") } 

componentWillMount 在16.2 之后官方不推荐使用了,这是因为 16.2 的时候 React 发生了一个改变,推出了几个新的生命周期,老的生命周期方法被替代掉了,不推荐使用。 那么为什么 React 要推出新的生命周期呢?

在 React 16.2 中通过对 diff 算法的更新,更加优化它的性能。提出了一个 Fiber 技术(纤维、分片、切片,比线程更小的一种概念)。因为我们传统的 React 它在创建、更新状态之后会创建新的虚拟 Dom 树,会对比老的虚拟 Dom 树,这个过程是同步的,如果数据量比较小还好。如果这个数据量非常多的情况下即组件非常多的情况下(例如:几百个组件),这个时候更新操作,会导致我们浏览器假死、卡顿,这个时候点什么都没有反应,因为它忙着新老虚拟 Dom 的对比,就是它在对比两个超级大的对象,里面包含了很多小对象,这时浏览器无法处理其它事件,所以导致卡顿影响体验。

所以这个东西就是一个边缘化的问题,你的组件达到这样一个程度,它真的会出现假死的情况。所以 React Fiber 技术就是来优化了虚拟 Dom 的 diff 算法,它把创建 Dom 和组件渲染整个过程拆分成了无数个小的分片任务来执行。可以认为这个任务无比的碎片化,这个时候如果有优先级较高的任务就先执行优先级较高的任务。当优先级较低的任务在执行时候突然来了优先级较高的任务,这个时候会打断正在执行的低优先级任务,先执行优先级高的任务。所谓的低优先级任务就是 componentWillMount 中去找哪些节点将要去挂载到页面中,而高优先级任务就是 render 正在渲染,didMount 挂载完成。这个时候我们低优先级的任务(找出那些需要更新的 Dom)这个过程是会被打断的,而我们更新 Dom 这个过程是不能被打断的,只能一鼓作气做完的,而 willMount 很不幸它是处在这个要找出来那些 Dom 是需要被更新的。所以这个过程是可以被打断的,所以可以认为 willMount 在忙着找出那些状态需要更新。因为接下来在 render 中就要开始更新了,didMount 就更新完成了。这个时候 willMount 找是处于低优先级的,而这个时候 render 正在更新,因为碎片化任务,他可能还不是同步的。即某个组件可能处在在找那个状态需要更新,那个 Dom 需要更新,而那边组件已经到了 render 渲染部分了,这个时候就吧低优先级的任务给砍掉了。砍掉怎么办,会保存吗?不会。只能下次再来一遍,再来找那个节点需要更新。所以这个生命周期就可能会触发多次这样一个问题(失去了唯一性),所以这是一个有隐患的生命周期方法,所以这里不推荐使用。

1.2 render

组件正式挂载渲染,只能访问 this.props 和 this.state,不允许修改状态和 Dom 输出。

1.3 componentDidMount

组件挂载完成,成功 render 并渲染完成真实 DOM 之后触发,可以访问、修改 Dom。

componentDidMount() {   // 数据请求axios   // 订阅函数调用   // setInterval   // 基于创建的完的dom进行初始化时候,例如 BetterScroll 使用   console.log("componentDidMount") } 

2. 运行中阶段

2.1 componentWillUpdate

组件即将更新,不能修改属性和状态,会造成死循环。非安全被弃用,同 componentWillMount。

// 组件即将更新 componentWillUpdate() {   console.log("componentWillUpdate") }  /* 在 16.2 之后版本使用会出现以下警告 ⚠️⚠️⚠️   react-dom.development.js:86 Warning: componentWillUpdate has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.  * Move data fetching code or side effects to componentDidUpdate. * Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.  Please update the following components: App   */  // 组件即将更新 - 强制去掉警告,UNSAFE 提示开发者这是一个不安全的生命周期方法。  UNSAFE_componentWillUpdate() {   console.log("componentWillUpdate") } 

2.2 render

组件正式挂载渲染,只能访问 this.props 和 this.state,不允许修改状态和 Dom 输出。

2.3 componentDidUpdate

组件更新完成,成功 render 并渲染完成真实 DOM 之后触发,可以访问、修改 Dom。

// 组件更新完成 - 接收两个行参,老的属性、老的状态 componentDidUpdate(prevProps, prevState) {   console.log(prevState)   console.log("componentDidUpdate") } 

2.4 shouldComponentUpdate

scu 控制组件是否应该更新,即是否执行 render 函数。

// 组件是否应该更新?- 接受两个行参,新的属性、新的状态 shouldComponentUpdate(nextProps, nextState) {   if (JSON.stringify(this.state) !== JSON.stringify(nextState)) {     return true   } else {     return false   } } 

2.5 componentWillReceiveProps

父组件修改属性触发,非安全被弃用,同 componentWillMount。处在 diff 中第一个阶段,找到哪些需要更新的 Dom。例如:如果父组件连续多次修改属性传递将触发多次 ajax 请求等。

// 父组件修改属性触发,应用在子组件中才有意义 componentWillReceiveProps(nextProps) {   // 最先获得父组件传来的属性,可以利用属性进行ajax或者逻辑处理   // 把属性转换为孩子的自己的状态等 } 

3. 销毁阶段

3.1 componentWillUnmount

在删除组件之前进行清理操作,比如计时器和事件监听器。

4. 新生命周期

4.1 getDerivedStateFromProps

getDerivedStateFromProps 第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子),返回一个对象作为新的 state,返回 null 则说明不需要在这里更新 state。

在初始化中代替 componentWillMount 。在父传子中能代替 componentWillReceiveProps。

这里不能做异步操作,因为这里 return 是立即返回的。

static getDerivedStateFromProps(nextProps, nextState) {   console.log("getDerivedStateFromProps")   return {    } } 

4.2 getSnapshotBeforeUpdate

getSnapshotBeforeUpdate 取代了 componentDidUpdate,触发时间为 update 发生的时候,在 render之后 Dom 渲染之前返回一个值,作为 componentDidUpdate 的第三个参数。

import React, { Component } from 'react'  export default class App extends Component {   state = {    }    // getDerivedStateFromProps 第一次的初始化组件以及后续的更新过程中(包括自身状态更新以及父传子),返回一个对象作为新的 state,返回 null 则说明不需要在这里更新 state   // 这里不能做异步操作,因为这里 return 是立即返回的   static getDerivedStateFromProps(nextProps, nextState) {     console.log("getDerivedStateFromProps")     return {      }   }    getSnapshotBeforeUpdate() {     return 100   }    componentDidUpdate(prevProps, prevState, value) {     console.log(value)   }     render() {     return (       <div>         <button onClick={()=>{           this.setState({            })         }}>修改</button>       </div>     )   } } 

5. React 中性能优化

5.1 shouldComponentUpdate

React 手动优化,控制组件自身或者子组件是否需要更新,尤其在子组件非常多的情况下,需要进行优化。

5.2 PureComponent

React 自动优化,PureComponent 会帮你比较新 props 跟旧的 props,新的 state 和老的 state(值相等,或者对象含有相同的属性、且属性值相等),决定 shouldComponentUpdate 返回 true 或者 false,从而决定要不要呼叫 render function。

注意:如果你的 state 或 props『永远都会变』,那 PureComponent 并不会比较快,因为 shallowEqual 也需要花时间,比如倒计时功能,这就不适合使用 Pure Component 了。

5.3 缓存技术

React.Component 是使用 ES6 classes 方式定义 React 组件的基类:

class Greeting extends React.Component {   render() {     return <h1>Hello, {this.props.name}</h1>;   } } 

PureComponent 和 memo 仅作为性能优化的方式而存在。但请不要依赖它来“阻止”渲染,因为这会产生 bug。PureComponnet 和 memo 都是通过对 props 值的浅比较来决定该组件是否需要更新的。

2.1 PureComponent (类组件)

React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate(),而 React.PureComponent 中以浅层对比 props 和 state 的方式来实现了该函数。
如果赋予 React 组件相同的 props 和 state,render() 函数会渲染相同的内容,那么在某些情况下使用 React.PureComponent 可提高性能。

2.2 memo(函数式组件)

函数组件缓存 memo,为啥起 memo 这个名字?在计算机领城,记记化是一种主要用来提高计算机程序速度的优化技术方案。它将开销较大的函数调用的返回结果存储起来,当同样的输入再次发生时,则这回缓存好的数据,以此提升运算效率。

React.memo 为高阶组件。它与 React.PureComponent 非常相似,但只适用于函数组件,而不适用 class 组件。

const MyComponent = function MyComponent(props) {   /* 使用 props 渲染 */ }; export default React.memo(MyComponent) 

如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果,即组件仅在它的 props 发生改变的时候进行重新渲染。通常来说,在组件树中 React 组件,只要有变化就会走一遍渲染流程。但是 React.memo(),我们可以仅仅让某些组件进行渲染。

React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState 或 useContext 的 Hook,当 context 发生变化时,它仍会重新渲染。

默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

function MyComponent(props) {   /* 使用 props 渲染 */ } function areEqual(prevProps, nextProps) {    } export default React.memo(MyComponent, areEqual); 

示例:

// 子组件代码: import React, { memo } from 'react'; const Child = ()=>{ 	console.log("2. 子组件渲染了") 	return (<div>子组件</div>) } export default Child  // 父组件代码: import React, { memo } from 'react'; import Child from './Child.jsx' const Father = ()=>{ 	const [name,setName]=React.useState(''); 	console.log("1. 父组件渲染了") 	return (<div> 		/* 在input框中输入内容,会走setName导致App组件重新渲染,但是子组件Child也会进行渲染。 */ 		父组件:<input type="text" value={name} onChange={ev=>setName(ev.target.value)} /> 		<Child /> 	</div>) } 
// 子组件代码: import React, { memo } from 'react'; const Child = ()=>{ 	console.log("2. 子组件渲染了") 	return (<div>子组件</div>) } export default memo(Child) // 父组件代码: import React, { memo } from 'react'; import Child from './Child.jsx' const Father = ()=>{ 	const [name,setName]=React.useState(''); 	console.log("1. 父组件渲染了") 	return (<div> 		/* 解决:子组件使用memo包起来 */ 		父组件:<input type="text" value={name} onChange={ev=>setName(ev.target.value)} /> 		<Child /> 	</div>) }