react - 生命周期

七、生命周期

组件的生命周期可分成三个状态:

  • Mounting(挂载,初始化):已插入真实 DOM
  • Updating(更新,运行时):正在被重新渲染
  • Unmounting(卸载,销毁):已移出真实 DOM

生命周期图谱可以参考链接:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

类组件如何实现类似vue的计算属性: https://zh-hans.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#what-about-memoization

$ cnpm i memoize-one -S

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'

// 引入react组件时,后缀名可以不写
// import App from './01_props/01_Parent_Child' // 父子组件
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽 多个插槽 
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随 
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态

// import App from './02_state/01_App_state_es6' // 初始化状态 - es6的构造函数
// import App from './02_state/02_App_state_es7' // 初始化状态 - es7 属性初始化器
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
import App from './02_state/05_App_computed' // 类组件实现类似于vue的计算属性

const root = ReactDOM.createRoot(document.querySelector('#root'))

root.render(<App />)

src/02_state/05_App_computed.jsx

// src/02_state/05_App_computed.jsx
// 输入框输入关键词,列表展示相关数据
import React, { Component } from 'react'

class Search extends Component {
  state = {
    word: '',
    count: 0 // 为了验证计算属性
  }

  render () {
    console.log('执行了')
    const filterList = this.props.list.filter(item => {
      return item.includes(this.state.word) && this.state.word !== ''
    })
    console.log(filterList)
    return (
      <div>
        <button onClick={
          () => {
            this.setState({ count: this.state.count + 1 })
          }
        }>加1</button>{ this.state.count }

        <br/> 

        <input onChange={ (event) => {
          this.setState({
            word: event.target.value
          })
        } } value = { this.state.word }/> { this.state.word }

        <ul>
          {
            // 不要在jsx代码中使用forEach,使用map
            filterList.map((item, index) => {
              return <li key = { index }>{ item }</li>
            })
          }
        </ul>
      </div>
    )
  }
}

export default class App extends Component {
  state = {
    list: [
      '你好呀',
      '很高兴为你服务',
      '好高兴',
      '你中午吃啥'
    ]
  }
  render() {
    return (
      <div>
        <Search list = { this.state.list }/>
      </div>
    )
  }
}


// src/02_state/05_App_computed.jsx
// 输入框输入关键词,列表展示相关数据
import React, { Component } from 'react'
import computed from 'memoize-one'
class Search extends Component {
  state = {
    word: '',
    count: 0 // 为了验证计算属性
  }

  filter = computed((list, word) => {
    console.log('11111') // 只有 list 和word 改变时才会重新计算
    return list.filter(item => {
      return item.includes(word) && word !== ''
    })
  })


  render () {
    console.log('执行了')
    // const filterList = this.props.list.filter(item => {
    //   return item.includes(this.state.word) && this.state.word !== ''
    // })

    const filterList = this.filter(this.props.list, this.state.word)
    // console.log(filterList)
    return (
      <div>
        <button onClick={
          () => {
            this.setState({ count: this.state.count + 1 })
          }
        }>加1</button>{ this.state.count }

        <br/> 

        <input onChange={ (event) => {
          this.setState({
            word: event.target.value
          })
        } } value = { this.state.word }/> { this.state.word }

        <ul>
          {
            // 不要在jsx代码中使用forEach,使用map
            filterList.map((item, index) => {
              return <li key = { index }>{ item }</li>
            })
          }
        </ul>
      </div>
    )
  }
}

export default class App extends Component {
  state = {
    list: [
      '你好呀',
      '很高兴为你服务',
      '好高兴',
      '你中午吃啥'
    ]
  }
  render() {
    return (
      <div>
        <Search list = { this.state.list }/>
      </div>
    )
  }
}

7.1 三个阶段

7.1.1 装载阶段

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  • constructor(): 在 React 组件挂载之前,会调用它的构造函数。

    如果不需要对类组件添加初始化数据以及绑定事件,那么就不需要写 constructor

  • static getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。

  • render(): render() 方法是 class 组件中唯一必须实现的方法。

  • componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用。

render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

7.1.2 更新阶段

每当组件的 state 或 props 发生变化时,组件就会更新。

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • static getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
  • shouldComponentUpdate():当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
  • render(): render() 方法是 class 组件中唯一必须实现的方法。
  • getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。
  • componentDidUpdate(): 在更新后会被立即调用,如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改。

render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。

7.1.3 卸载阶段

当组件从 DOM 中移除时会调用如下方法:

  • componentWillUnmount(): 在组件卸载及销毁之前直接调用。

7.1.4 Error boundaries

Error boundaries 是 React 组件,它会在其子组件树中的任何位置捕获 JavaScript 错误,并记录这些错误,展示降级 UI 而不是崩溃的组件树。Error boundaries 组件会捕获在渲染期间,在生命周期方法以及其整个树的构造函数中发生的错误。

项目中需要使用的最多的生命周期的钩子函数为 render, componentDidMount,componentDidUpdate,componentWillUnmount

详细介绍范例:https://zhuanlan.zhihu.com/p/392532496

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'

// 引入react组件时,后缀名可以不写
// import App from './01_props/01_Parent_Child' // 父子组件
// import App from './01_props/02_Parent_Child_value' // 父组件给子组件传值
// import App from './01_props/03_Parent_Child_default' // 父组件给子组件传值,子组件设置默认值
// import App from './01_props/04_Parent-Child_type' // 父组件给子组件传值,子组件设置默认值,验证数据类型
// import App from './01_props/05_App_props_children' // 类插槽
// import App from './01_props/06_App_mutiple_props_children' // 类具名插槽 多个插槽 
// import App from './01_props/07_App_mouse_tracker' // 鼠标跟随 
// import App from './01_props/08_App_render_props' // 渲染属性 - 其他组件共享状态

// import App from './02_state/01_App_state_es6' // 初始化状态 - es6的构造函数
// import App from './02_state/02_App_state_es7' // 初始化状态 - es7 属性初始化器
// import App from './02_state/03_App_setState_function' // 修改状态 传递函数
// import App from './02_state/04_App_setState_object' // 修改状态 传递对象
// import App from './02_state/05_App_computed' // 类组件实现类似于vue的计算属性
import App from './02_state/06_App_lifeCycle' // 生命周期

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的 UI
    return { hasError: true };
  }

  // try catch 
  componentDidCatch(error, errorInfo) {
    // 你同样可以将错误日志上报给服务器
    // logErrorToMyService(error, errorInfo);
    console.log(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定义降级后的 UI 并渲染
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

const root = ReactDOM.createRoot(document.querySelector('#root'))

root.render(
  <ErrorBoundary>
         <App root={root}/>
  </ErrorBoundary>
)

src/02_state/06_App_lifeCycle.jsx

// src/02_state/06_App_lifeCycle.jsx
import React, { Component } from 'react';

// 挂载阶段 - 初始化阶段
//   constructor 初始化状态以及事件绑定
//   static getDerivedStateFromProps 让组件在 props 变化时更新 state
//   render
//   componentDidMount  等同于vue中的mounted

// 更新阶段 - 运行时阶段
//   static getDerivedStateFromProps
//   shouldComponentUpdate react组件提升性能的关键 返回值false 表示不更新
//   render
//   getSnapshotBeforeUpdate
//   componentDidUpdate  除了类似vue的updated,还可以实现watch的功效,条件请求数据

// 卸载阶段 - 销毁阶段
//  componentWillUnmount


class App extends Component {
  // constructor (props) { // Useless constructor
  //   super(props)
  // }
  state = { count: 100 }
  // static getDerivedStateFromProps (props, state) { // 一般不使用
  //   // getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
  //   // 它应返回一个对象来更新 state,如果返回 null 则不更新任何内容
  //   // state 的值在任何时候都取决于 props
  // }

  componentDidMount () {
    // 等同于 vue中的 mounted
    // 数据请求,实例化操作,DOM操作,定时器,计时器,订阅数据变化
    // 修改状态
    this.setState({ count: this.state.count + 100 })
  }

  shouldComponentUpdate (nextProps, nextState) {
    // 可以作为react组件的性能优化的手段,但是也要慎用
    // return true
    return false
  }
  // getSnapshotBeforeUpdate(prevProps, prevState) {
  //   // 在最近一次渲染输出(提交到 DOM 节点)之前调用。
  //   // 它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。
  //   // 此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。
  // }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 参照vue中 updated
    // 实例化操作,DOM操作, 特定条件可以请求数据以及修改数据
    // if (this.props.userID !== prevProps.userID) { // 监听数据的变化
    //   this.fetchData(this.props.userID);
    // }
  }

  componentWillUnmount () {
    // 参照vue  beforeDestory
    // 清理对象,取消订阅,消除定时器计时器
    // 当count的值等于210的时候销毁组件
  }
  render() {
    // 挂载阶段 依据初始化数据渲染数据
    // 更新阶段 当该组件的状态或者属性发生改变时触发此函数,也就输数据的改变引起视图的二次渲染
    return (
      <div>
        <p>{ this.state.count }</p>
        <button onClick={ () => {
          if (this.state.count === 210) {
            console.log('销毁组件')
            // 销毁组件
            this.props.root.unmount()
          } else {
            this.setState({ count: this.state.count + 1 } )
          }
        } }>加</button>
      </div>
    );
  }
}

export default App;

7.2 两个时期

将应用的渲染过程分为mount阶段(应用首次渲染)和update阶段(应用状态更新),无论在mount阶段还是update阶段,都会经历两个子阶段,一个是render阶段,一个是commit阶段。

mount时:在render阶段会根据jsx对象构建新的workInProgressFiber树,然后将相应的fiber节点标记为Placement,表示这个fiber节点需要被插入到dom树中,然后会这些带有副作用的fiber节点加入一条叫做Effect List的链表中。在commit阶段会遍历render阶段形成的Effect List,执行链表上相应fiber节点的副作用,比如Placement插入,或者执行Passive(useEffect的副作用)。将这些副作用应用到真实节点上update时:在render阶段会根据最新状态的jsx对象对比current Fiber,再构建新的workInProgressFiber树,这个对比的过程就是diff算法diff算法又分成单节点的对比和多节点的对比,对比的过程中同样会经历收集副作用的过程,也就是将对比出来的差异标记出来,加入Effect List中,这些对比出来的副作用例如:Placement(插入)、Update(更新)、Deletion(删除)等。在commit阶段同样会遍历Effect List,将这些fiber节点上的副作用应用到真实节点上。

参考链接: https://blog.csdn.net/bemystery/article/details/121897223

7.3 入门理解React Fiber架构

在 React 16 之前,VirtualDOM 的更新采用的是Stack架构实现的,也就是循环递归方式。不过,这种对比方式有明显的缺陷,就是一旦任务开始进行就无法中断,如果遇到应用中组件数量比较庞大,那么VirtualDOM 的层级就会比较深,带来的结果就是主线程被长期占用,进而阻塞渲染、造成卡顿现象。

为了避免出现卡顿等问题,我们必须保障在执行更新操作时计算时不能超过16ms,如果超过16ms,就需要先暂停,让给浏览器进行渲染,后续再继续执行更新计算。而Fiber架构就是为了支持“可中断渲染”而创建的。

React中,Fiber使用了一种新的数据结构fiber tree,它可以把虚拟dom tree转换成一个链表,然后再执行遍历操作,而链表在执行遍历操作时是支持断点重启的,示意图如下。imagepng

官方介绍中,Fiber 被理解为是一种数据结构,但是我们也可以将它理解为是一个执行单元。

Fiber 可以理解为一个执行单元,每次执行完一个执行单元,React Fiber就会检查还剩多少时间,如果没有时间则将控制权让出去,然后由浏览器执行渲染操作。React Fiber 与浏览器的交互流程如下图。imagepng

可以看到,React 首先向浏览器请求调度,浏览器在执行完一帧后如果还有空闲时间,会去判断是否存在待执行任务,不存在就直接将控制权交给浏览器;如果存在就会执行对应的任务,执行完一个新的任务单元之后会继续判断是否还有时间,有时间且有待执行任务则会继续执行下一个任务,否则将控制权交给浏览器执行渲染,这个流程是循环进行的。

所以,我们可以将Fiber 理解为一个执行单元,并且这个执行单元必须是一次完成的,不能出现暂停。并且,这个小的执行单元在执行完后计算之后,可以移交控制权给浏览器去响应用户,从而提升了渲染的效率。

在官方的文档中,Fiber 被解释为是一种数据结构,即链表结构。在链表结构中,每个 Virtual DOM 都可以表示为一个 fiber,如下图所示。imagepng通常,一个 fiber包括了 child(第一个子节点)、sibling(兄弟节点)、return(父节点)等属性,React Fiber 机制的实现,就是依赖于上面的数据结构。

通过介绍,我们知道Fiber使用的是链表结构,准确的说是单链表树结构。为了方便理解 Fiber 的遍历过程,下面我们就看下Fiber链表结构。

imagepng

在上面的例子中,每一个单元都包含了payload(数据)和nextUpdate(指向下一个单元的指针)两个元素

参考链接:https://segmentfault.com/a/1190000042271919

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值