每天学点React - 组件的生命周期(三)

80 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第17天,点击查看活动详情

组件的生命周期(旧)

昨天说了旧版生命周期的三个阶段

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

    1. constructor()
    2. componentWillMount()
    3. render()
    4. componentDidMount() ----- 常用于初始化的相关处理逻辑,例如:开启定时器,发送请求,订阅消息等...
  2. 更新阶段:由组件内部this.setState()或父组件render触发

    1. shouldComponentUpdate()
    2. componentWillUpdate()
    3. render()
    4. componentDidUpdate()
  3. 卸载组件:由ReactDOM.unmountComponentAtNode()触发

    1. componentWillUnmount() ----- 常用于销毁组件时进行收尾,例如:关闭定时器,取消订阅等...

组件的生命周期(新)

不知道有没有小伙伴还记得上一篇中有一个警告提示呢?

image.png

在项目中,我用的是新版的React,调用生命周期勾子时,控制台给出了这个警告。

在官网的说明中,这样的函数一共有三个:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

这三个生命周期方法经常会被误解和滥用,且预计在一部渲染中,会潜在更大的问题,因此需要将这些生命周期添加"UNSAFE_"前缀。(这里的 “unsafe” 不是指安全性,而是表示使用这些生命周期的代码在 React 的未来版本中更有可能出现 bug,尤其是在启用异步渲染之后。)

// 组件挂载前执行
UNSAFE_componentWillMount() {
    console.log('Count: componentWillMount')
}

// 组件更新前执行
UNSAFE_componentWillUpdate() {
    console.log('Count: componentWillUpdate')
}

// 父组件调用render执行
UNSAFE_componentWillReceiveProps(){
    console.log('Children: componentWillReceiveProps')
}

加上"UNSAFE_"前缀后,可以看到控制台的警告已经没有了: image.png

我们再来聊一聊新版的生命周期吧,先把流程图贴上:

image.png 通过与旧版生命周期流程图的对比,我们可以看到,原先旧版挂载时的componentWillMount不见了,取而代之的是一个新的勾子函数getDerivedStateFromProps

而针对于更新时,旧版的componentWillReceivePropscomponentWillUpdate两个不见了,新版的多出来getDerivedStateFromPropsgetSnapshotBeforeUpdate这两个。

getDerivedStateFromProps

横跨了挂载和更新,我们先来看看它是怎么样的

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

<head>
    <meta charset="UTF-8">
    <title>react的生命周期(旧)</title>
</head>

<body>
    <!-- 测试容器 -->
    <div id="test"></div>

    <!-- 加载 React 核心库 -->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 加载 React DOM 用于支持 React 操作 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 }
            }

            getDerivedStateFromProps(){
                console.log('Count: getDerivedStateFromProps')
            }

            // 组件挂载后执行
            componentDidMount() {
                console.log('Count: componentDidMount')
            }

            // 控制组件更新阀门
            shouldComponentUpdate() {
                console.log('Count: shouldComponentUpdate')
                return true
                // return false
            }

            // 组件更新后执行
            componentDidUpdate() {
                console.log('Count: componentDidUpdate')
            }

            // 挂载组件
            render() {
                console.log('Count: render')
                const { count } = this.state
                return (
                    <div>
                        <h2>当前求和为:{count}</h2>
                        <button onClick={this.addOne}>增加 1</button>
                        <button onClick={this.unmount}>卸载组件</button>
                        <button onClick={this.force}>强制更新</button>
                    </div>
                )
            }

            // 按钮回调
            addOne = () => {
                // 获取原状态
                const { count } = this.state
                // 更新状态
                this.setState({ count: count + 1 })
            }

            // 卸载按钮回调
            unmount = () => {
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }

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

        ReactDOM.render(<Count />, document.getElementById("test"))
    </script>
</body>

</html>

image.png

从运行结果图中可以看到,该勾子函数并没有在控制台中打印出来,反而还给了我们一个异常提示,提示我们说该函数被定义为实例方法,将被忽略,我们需要将其申明为静态方法。

加上 static 再看看效果:

static getDerivedStateFromProps(){
    console.log('Count: getDerivedStateFromProps')
}

image.png

该函数被执行了,但是又有新的问题出现,提示我们得返回一个有效的状态对象或者null

static getDerivedStateFromProps(){
    console.log('Count: getDerivedStateFromProps')
    return null
}

先返回一个null测试看看效果,可以看到正常显示:

image.png

再来返回一个状态对象看一下:

static getDerivedStateFromProps(){
    console.log('Count: getDerivedStateFromProps')
    return {count: 888}
}

image.png

可以看到返回了状态对象之后,我们针对state做的更新操作都没受到影响,该状态属性count的值仍旧为: 888。这功能好像没啥用是吧?别急,根据该函数的名字,我们可以联想到其应该是可以获取到props的,给它加上参数看看。

static getDerivedStateFromProps(props){
    console.log('Count: getDerivedStateFromProps', props)
    return props
}

ReactDOM.render(<Count count={668}/>, document.getElementById("test"))

image.png

通过案例可以看到我们通过props得到了一个状态,那我们什么时候才会用到这个方法呢?在官网中已经有给出相关的案例了,我们来看一下:

image.png

具体案例可以参考官网给出的相关链接:react.docschina.org/blog/2018/0…

getSnapshotBeforeUpdate

            getSnapshotBeforeUpdate() {
                console.log('Count: getsnapshotBeforeUpdate')
            }

image.png

顺序是对的,但是控制台给出了异常的警告提示我们需要有返回值。对于null我们知道,但是快照是什么呢,我们去官网查看下说明:

image.png

原来它可以接收两个参数,并将其传递给componentDidUpdate勾子函数使用,我们来试试看:

getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log('Count: getsnapshotBeforeUpdate', prevProps, prevState)
    return "snapshot"
}

componentDidUpdate(prevProps, prevState, snapshotValue) {
    console.log('Count: componentDidUpdate', prevProps, prevState, snapshotValue)
}

image.png

图中可以看到componentDidUpdate确实是接收到了getSnapshotBeforeUpdate传递的几个参数。利用getSnapshotBeforeUpdate函数,我们就可以在组件更新之前先保存当前的快照,将其传递给componentDidUpdate,让我们可以在更新之后拿到更新前的值。