第十三章 React生命周期(新)

在新版本的生命周期中废弃了3个钩子函数,新增了2个钩子函数:

第十三章 React生命周期(新)_第1张图片

从上图可知,新增的2个钩子函数是我们从来没有见过的:

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

废弃的3个钩子函数是:

  • componentWillMount
  • componentWillUpdate
  • componentWillReceiveProps

根据官方文档说明:在React17版本中这废弃的3个钩子函数被重命名了,需要加一个前缀UNSAFE_才能使用。但是在18.x版本中就会彻底删除这3个钩子函数。以下是你在新版的React中继续使用这3个钩子函数时报的警告:

警告:componentWillMount已重命名,不建议使用。详情见https://reactjs.org/link/unsafe-component-lifecycles。

移动带有副作用的代码到componentDidMount,并在构造函数中设置初始状态。
将componentWillMount重命名为UNSAFE_componentWillMount以在非严格模式下抑制此警告。在React 18.x,只有UNSAFE_名称将工作。要将所有已弃用的生命周期重命名为它们的新名称,您可以在项目源文件夹中运行' npx react-codemod rename-unsafe-lifecycles '

使用getDerivedStateFromProps

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>生命周期Count</title>
</head>

<body>
  <!-- 准备好员工“容器” -->
  <div id="app"></div>

  <!-- 引入ReactJS核心库 -->
  <script type="text/javascript" src="../JS/新版本/react.development.js"></script>

  <!-- 引入React-DOM核心库,用于操作DOM -->
  <script type="text/javascript" src="../JS/新版本/react-dom.development.js"></script>

  <!-- 引入Babel,用于编译jsx为js -->
  <script type="text/javascript" src="../JS/新版本/babel.min.js"></script>

  <!-- 此处类型为babel -->
  <script type="text/babel">
    class Count extends React.Component {

      constructor(props){
        console.log('Count--constructor')
        super(props)

        // 初始化状态
        this.state = { count: 0 }
      }

      // 卸载组件
      death = () => {
        ReactDOM.unmountComponentAtNode(document.getElementById('app'))
      }

      // 求和加1
      add = () => {
        let { count } = this.state
        count += 1
        this.setState({count})
      }

      // 强制更新
      force = () => {
        this.forceUpdate()
      }

      getDerivedStateFromProps(){
        console.log("Count--getDerivedStateFromProps")
      }

      // 组件挂载完毕
      componentDidMount() {
        console.log("Count--componentDidMount")
      }

      // 判断组件是否需要更新,返回一个false或者true
      shouldComponentUpdate(){
        console.log("Count--shouldComponentUpdate")
        return true
      }

      // 组件更新完毕
      componentDidUpdate(){
        console.log("Count--componentDidUpdate")
      }

      // 组件将要被卸载
      componentWillUnmount(){
        console.log("Count--componentWillUnmount")
      }

      // 渲染DOM结构
      render () {
        console.log("Count--render")
        const {count} = this.state
        return (
          <div>
            <h1>当前求和为:{count}</h1>
            <button onClick={this.add}>点我+1</button>
            <button onClick={this.force}>强制更新</button>
            <button onClick={this.death}>卸载组件</button>
          </div>
        )
      }
    }
    // 2、将虚拟DOM渲染到页面,标签必须闭合
    ReactDOM.render(<Count />, document.getElementById('app'))
  </script>
</body>
</html>

我们还是使用Count组件作为案例使用。但是报错了:

Warning: Count: getDerivedStateFromProps() is defined as an instance method and will be ignored. Instead, declare it as a static method.

大概意思是:警告:Count: getDerivedStateFromProps()被定义为实例方法,将被忽略。相反,应该将其声明为静态方法。

意思是这个一个静态方法,我们需要加一个关键字:static

static getDerivedStateFromProps(){
        console.log("Count--getDerivedStateFromProps")
      }

但是又有了一个新的错误:

Warning: Count.getDerivedStateFromProps(): A valid state object (or null) must be returned. You have returned undefined.

大概意思是:Count.getDerivedStateFromProps():必须返回一个有效的状态对象(或null)。您已经返回undefined

这个错误是说我们要返回一个和state对象或者是null,只有这2种选择,而不是undefined

static getDerivedStateFromProps(){
        console.log("Count--getDerivedStateFromProps")
        return null
      }

这次我们返回null试试水,发现没有报错,且打印出了生命周期顺序:

Count--constructor
Count--getDerivedStateFromProps
Count--render
Count--componentDidMount

那什么又是返回一个状态对象呢?我们看看官网:

static getDerivedStateFromProps()

static getDerivedStateFromProps(props, state)

getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。例如,实现 组件可能很方便,该组件会比较当前组件与下一组件,以决定针对哪些组件进行转场动画。

派生状态会导致代码冗余,并使组件难以维护。 确保你已熟悉这些简单的替代方案:

  • 如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改,请改用 componentDidUpdate
  • 如果只想在 prop 更改时重新计算某些数据,请使用 memoization helper 代替。
  • 如果你想在 prop 更改时“重置”某些 state,请考虑使组件完全受控或使用 key 使组件完全不受控 代替。

此方法无权访问组件实例。如果你需要,可以通过提取组件 props 的纯函数及 class 之外的状态,在getDerivedStateFromProps()和其他 class 方法之间重用代码。

请注意,不管原因是什么,都会在每次渲染前触发此方法。这与 UNSAFE_componentWillReceiveProps 形成对比,后者仅在父组件重新渲染时触发,而不是在内部调用 setState 时。

我们发现一句话非常重要:此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props。

我们以此来修改代码:

static getDerivedStateFromProps(props,state){
        console.log("Count--getDerivedStateFromProps",props,state)
        return props
      }
//...
ReactDOM.render(<Count count={110} />, document.getElementById('app'))

我们在标签属性上面写了一个count={110},并在该函数中返回props,此时我们发现页面中显示的内容是:110,且点击自增按钮,页面不发生任何改变。但是这个函数我们使用的极少,就此作为了解。


使用getSnapshotBeforeUpdate

 getSnapshotBeforeUpdate(){
        console.log("Count--getSnapshotBeforeUpdate")
      }

此时我们更新组件时报了一个错误:

Warning: Count.getSnapshotBeforeUpdate(): A snapshot value (or null) must be returned. You have returned undefined.

大概意思是:警告:Count.getSnapshotBeforeUpdate():必须返回快照值(或null)。您已经返回undefined

和之前错误类似,同样我们返回null试试水:

 getSnapshotBeforeUpdate(){
        console.log("Count--getSnapshotBeforeUpdate")
        return null
      }

此时我们更新组件不报错,且打印出了执行的生命周期:

Count--getDerivedStateFromProps {count: 110} {count: 2}
Count--shouldComponentUpdate
Count--render
Count--getSnapshotBeforeUpdate
Count--componentDidUpdate

同样的问题:什么是返回一个快照值?我们同样看看官网:

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()

此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。

应返回 snapshot 的值(或 null)。

意思是它能在组件即将渲染到页面之前,我们可以通过它拿到一些DOM信息,并传递给componentDidUpdate()使用。

我们写一个案例说明:

DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>测试生命周期函数getSnapshotBeforeUpdatetitle>
  <style>
    .list {
      width: 200px;
      height: 150px;
      background-color: pink;
      overflow: auto;
    }

    .news {
      width: 100%;
      height: 30px;
    }
  style>
head>

<body>
  
  <div id="app">div>

  
  <script type="text/javascript" src="../JS/新版本/react.development.js">script>

  
  <script type="text/javascript" src="../JS/新版本/react-dom.development.js">script>

  
  <script type="text/javascript" src="../JS/新版本/babel.min.js">script>

  
  <script type="text/babel">
    class Count extends React.Component {
      listRef = React.createRef()
      state = { newArr: [] }

      getSnapshotBeforeUpdate () {

        return this.listRef.current.scrollHeight
      }
      // 组件更新完毕
      componentDidUpdate (prevProps,prevState,snapshotValue) {
        console.log("Count--componentDidUpdate",snapshotValue)
        this.listRef.current.scrollTop += this.listRef.current.scrollHeight - snapshotValue
      }

      // 组件挂载完毕
      componentDidMount () {
        setInterval(() => {
          let { newArr } = this.state
          const news = "新闻" + (newArr.length + 1)
          newArr = [news, ...newArr]

          this.setState({ newArr })
        }, 1000)

      }

      // 渲染DOM结构
      render () {
        const { newArr } = this.state
        return (
          <div className="list" ref={this.listRef}>
            {
              newArr.map((val, idx) => {
                return <div className="news" key={idx}>{val}</div>
              })
            }
          </div>
        )
      }
    }
    // 2、将虚拟DOM渲染到页面,标签必须闭合
    ReactDOM.render(<Count count={110} />, document.getElementById('app'))
  script>
body>
html>

以上组件的效果如下:

第十三章 React生命周期(新)_第2张图片

就是让滚动条一直处于底部,让最初的新闻信息一直停留在当前可见区域。大概主要的代码逻辑如下:

 // 组件挂载完毕
      componentDidMount () {
        setInterval(() => {
          let { newArr } = this.state
          const news = "新闻" + (newArr.length + 1)
          newArr = [news, ...newArr]

          this.setState({ newArr })
        }, 1000)

      }

设置定时器,每个一秒就增加一条新闻信息。

getSnapshotBeforeUpdate () {
    return this.listRef.current.scrollHeight
}

在组件更新快要完成时,获取组件的滚动条高度。

 // 组件更新完毕
 componentDidUpdate (prevProps,prevState,snapshotValue) {
     console.log("Count--componentDidUpdate",snapshotValue)
     this.listRef.current.scrollTop += this.listRef.current.scrollHeight - snapshotValue
 }

在组件更新完成时,设置滚动条的scrollTop属性,使得内容一直在可见区域。


小总结

  • 新的生命周期的三个阶段

1、初始化阶段:由ReactDOM.render()触发—初次渲染

  • 1===>constructor()
  • 2===>getDerivedStateFromProps()
  • 3===>render()
  • 4===>componentDidMount()

2、更新阶段:由组件内部this.setState()或者父组件render触发

  • 1===>getDerivedStateFromProps

  • 2===>shouldComponentUpdate()

  • 3===>render()

  • 4===>getSnapshotBeforeUpdate()

  • 5===>componentDidUpdate()

3、卸载组件:由ReactDOM.unmountComponentAtNode()触发

  • 1===> componentWillUnmount()

  • 重要的钩子

1、render: 初始化渲染或更新渲染调用

2、componentDidMount: 开启监听,发送ajax请求

3、componentWillUnmount:做一些收尾的工作,如:销毁监听,清理定时器


  • 即将废弃的钩子

1、componentWillMount

2、componentWillUpdate

3、componentWillReceiveProps

17.x版本使用需要加上UNSAFE_前缀使用,18.x版本彻底废弃,不建议使用。

你可能感兴趣的:(ReactJS,react.js,javascript,前端,react生命周期)