聊聊React v16.3的UNSAFE类生命周期

栏目: 服务器 · 发布时间: 5年前

内容简介:前缀?案例一:如下是安装时监听外部事件调度程序的组件示例
聊聊React v16.3的UNSAFE类生命周期
不知道小伙伴有没有注意到,自从react更新到16.3版本后,以前使用的 componentWillMountcomponentWillReceivePropscomponentWillUpdate 三个生命周期函数都有eslint报警,让我们使用 UNSAFE_ 前缀的新的生命周期函数。不禁有疑问“react这是意欲何为啊”?为什么要加 UNSAFE_

前缀?

为什么要对这些生命周期函数报警?

1、componentWillMount

componentWillMount 生命周期发生在首次渲染前,一般使用的小伙伴大多在这里初始化数据或异步获取外部数据赋值。初始化数据,react官方建议放在 constructor 里面。而异步获取外部数据,渲染并不会等待数据返回后再去渲染。

案例一:如下是安装时监听外部事件调度程序的组件示例

class Example extends React.Component {   
    state = {
        value: ''
    };
    componentWillMount() {    
        this.setState({       
            value: this.props.source.value
        });       
        this.props.source.subscribe(this.handleChange);
    }   
    componentWillUnmount() {    
        this.props.source.unsubscribe(this.handleChange ); 
    }   
    handleChange = source => {    
        this.setState({
            value: source.value
        });   
    }; 
}
复制代码

试想一下,假如组件在第一次渲染的时候被中断,由于组件没有完成渲染,所以并不会执行 componentWillUnmount 生命周期( 注:很多人经常认为componentWillMount和componentWillUnmount总是配对,但这并不是一定的。只有调用componentDidMount后,React才能保证稍后调用componentWillUnmount进行清理 )。因此 handleSubscriptionChange 还是会在数据返回成功后被执行,这时候 setState 由于组件已经被移除,就会导致内存泄漏。所以建议把异步获取外部数据写在 componentDidMount 生命周期里,这样就能保证 componentWillUnmount 生命周期会在组件移除的时候被执行,避免内存泄漏的风险。

现在,小伙伴清楚为什么了要用 UNSAFE_componentWillMount 替换 componentWillMount 了吧( 注意:这里的UNSAFE并不是指安全性,而是表示使用这些生命周期的代码将更有可能在未来的React版本中存在缺陷,特别是一旦启用了异步渲染

2、componentWillReceiveProps

componentWillReceiveProps 生命周期是在props更新时触发。一般用于 props 参数更新时同步更新state参数。但如果在 componentWillReceiveProps 生命周期直接调用父组件的某些有调用 setState 的函数,会导致程序死循环。

案例二:如下是子组件 componentWillReceiveProps 里调用父组件改变state的函数示例

...
class Parent extends React.Component{
    constructor(){
        super();
        this.state={
            list: [],
            selectedData: {}
        };
    }
    
    changeSelectData = selectedData => {
        this.setState({
            selectedData
        });
    }
    
    render(){
        return (
            <Clild list={this.state.list} changeSelectData={this.changeSelectData}/>
        );
    }
}

...
class Child extends React.Component{
    constructor(){
        super();
        this.state={
            list: []
        };
    }
    componentWillReceiveProps(nextProps){
        this.setState({
            list: nextProps.list
        })
        nextProps.changeSelectData(nextProps.list[0]); //默认选择第一个
    }
    ...
}
复制代码

如上代码,在 Child 组件的 componentWillReceiveProps 里直接调用 Parent 组件的 changeSelectData 去更新 Parent 组件 stateselectedData 值。会触发 Parent 组件重新渲染,而 Parent 组件重新渲染会触发 Child 组件的 componentWillReceiveProps 生命周期函数执行。如此就会陷入死循环。导致程序崩溃。

所以,React官方把 componentWillReceiveProps 替换为 UNSAFE_componentWillReceiveProps ,让小伙伴在使用这个生命周期的时候注意它会有缺陷,要注意避免,比如上面例子, ChildcomponentWillReceiveProps 调用 changeSelectData 时先判断 list 是否有更新再确定是否要调用,就可以避免死循环。

3、componentWillUpdate

componentWillUpdate 生命周期在视图更新前触发。一般用于视图更新前保存一些数据方便视图更新完成后赋值。 案例三:如下是列表加载更新后回到当前滚动条位置的案例

class ScrollingList extends React.Component {   
    listRef = null;   
    previousScrollOffset = null;   
    componentWillUpdate(nextProps, nextState) {    
        if (this.props.list.length < nextProps.list.length) {      
            this.previousScrollOffset = this.listRef.scrollHeight - this.listRef.scrollTop;    
        } 
    }   
    componentDidUpdate(prevProps, prevState) {    
        if (this.previousScrollOffset !== null) {      
            this.listRef.scrollTop = this.listRef.scrollHeight - this.previousScrollOffset;  
            this.previousScrollOffset = null;    
        }   
    }   
    render() {    
        return (       
            `<div>` {/* ...contents... */}`</div>`     
        );   
    }   
    setListRef = ref => {    this.listRef = ref;   };
}
复制代码

由于 componentWillUpdatecomponentDidUpdate 这两个生命周期函数有一定的时间差( componentWillUpdate 后经过渲染、计算、再更新 DOM 元素,最后才调用 componentDidUpdate ),如果这个时间段内用户刚好拉伸了浏览器高度,那 componentWillUpdate 计算的 previousScrollOffset 就不准确了。如果在 componentWillUpdate 进行 setState 操作,会出现多次调用只更新一次的问题,把 setState放componentDidUpdate ,能保证每次更新只调用一次。

所以,react官方建议把 componentWillUpdate 替换为 UNSAFE_componentWillUpdate 。如果真的有以上案例的需求,可以使用16.3新加入的一个周期函数 getSnapshotBeforeUpdate 。下面会有具体说明,这里暂时卖个关子。

有什么替换方案?

1、getDerivedStateFromProps

getDerivedStateFromProps 是官方在16.3新加入的生命周期函数, props 变化时被调用,若是父组件重新渲染,也会被调用。它返回新的 props 值。

案例四:如下是 getDerivedStateFromProps 的使用实例

class Example extends React.Component {   
    static getDerivedStateFromProps(nextProps, prevState) { 
        if(nextProps.name !== prevState.name) {
            return {
                name: nextProps.name
            }
        }
    } 
}
复制代码

可以看到, getDerivedStateFromProps 接收最新的 PropsnextProps 、上一个 stateprevState 两个参数,返回返回一个对象来更新 state ,或者返回 null 表示不需要更新 state 。要注意的是, getDerivedStateFromProps 不能访问 this ,所以如果要跟上一个 props 值作比较,只能是把上一个 props 值存到 state 里作为镜像。到这里你一定有疑问,为什么不把上一个 props 值传给 getDerivedStateFromProps ?官方给的解析如下:

  • 在第一次调用 getDerivedStateFromProps (实例化后)时, prevProps 参数将为 null ,需要在访问 prevProps 时添加 if-not-null 检查。

  • 没有将以前的 props 传递给这个函数,在未来版本的React中释放内存的一个步骤。 (如果React不需要将先前的道具传递给生命周期,那么它不需要将先前的道具对象保留在内存中。)

综上可知, getDerivedStateFromProps 正是官方新加入的用以替代 componentWillReceiveProps 的方案。如果说,你的项目会考虑往后的版本兼容,建议改用 getDerivedStateFromProps

2、getSnapshotBeforeUpdate

getSnapshotBeforeUpdate 是跟 getDerivedStateFromProps 一起,在16.3新加入的生命周期函数。触发的时机在最近的更改被提交到 DOM 元素前,使得组件可以在更改之前获得当前值,此生命周期返回的任意值都会作为第三个参数传给 componentDidUpdate 。一般当我们需要在更新 DOM 前需要保存 DOM 当前的状态时会使用这个生命周期,比较常见是用于 DOM 更新前获取滚动位置,更新后恢复到该滚动位置。比如上面的案例三, componentWillUpdate 更好的替换方案就是 getSnapshotBeforeUpdategetSnapshotBeforeUpdatecomponentDidUpdate 只经过了更新 DOM 这一操作。

案例五:如下为案例三的更好的替换方案

class ScrollingList extends React.Component {   
    listRef = null;   
    getSnapshotBeforeUpdate(prevProps, prevState) {    
        if (prevProps.list.length < this.props.list.length) {      
            return this.listRef.scrollHeight - this.listRef.scrollTop;    
        } 
        return null;
    }   
    componentDidUpdate(prevProps, prevState, snapshot) {    
        if (snapshot !== null) {      
            this.listRef.scrollTop = this.listRef.scrollHeight - snapshot;   
        }   
    }   
    render() {    
        return (       
            `<div>` {/* ...contents... */}`</div>`     
        );   
    }   
    setListRef = ref => {    this.listRef = ref;   };
}
复制代码

最后,关于 componentWillMount 的替换方案,官方建议把该生命周期函数的逻辑处理放到 componentDidMount 里面去。

随着React版本迭代,会否兼容UNSAFE类生命周期

React官网上的计划是:

  • 16.3:为不安全生命周期引入别名 UNSAFE_componentWillMountUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate 。 (旧的生命周期名称和新的别名都可以在此版本中使用。)

  • 未来的16.x版本:为 componentWillMountcomponentWillReceivePropscomponentWillUpdate 启用弃用警告。 (旧的生命周期名称和新的别名都可以在此版本中使用,但旧名称会记录DEV模式警告。)

  • 17.0:删除 componentWillMountcomponentWillReceivePropscomponentWillUpdate 。 (从现在开始,只有新的“UNSAFE_”生命周期名称将起作用。)

结论

其实,说了这么多。就两点:

1、React意识到 componentWillMountcomponentWillReceivePropscomponentWillUpdate 这三个生命周期函数有缺陷,比较容易导致崩溃。但是由于旧的项目已经在用以及有些老开发者习惯用这些生命周期函数,于是通过给它加 UNSAFE_ 来提醒用它的人要注意它们的缺陷。

2、React加入了两个新的生命周期函数 getSnapshotBeforeUpdategetDerivedStateFromProps ,目的为了即使不使用这三个生命周期函数,也能实现 只有这三个生命周期能实现 的功能。

ps:本文 部分内容 借鉴参考文章 ReactV16.3即将更改的生命周期


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

重构

重构

Martin Fowler / 熊节 / 中国电力出版社 / 2003-8-1 / 68.00元

Martin Fowler和《重构:改善既有代码的设计》(中文版)另几位作者清楚揭示了重构过程,他们为面向对象软件开发所做的贡献,难以衡量。《重构:改善既有代码的设计》(中文版)解释重构的原理(principles)和最佳实践方式(best practices),并指出何时何地你应该开始挖掘你的代码以求改善。《重构:改善既有代码的设计》(中文版)的核心是一份完整的重构名录(catalog of r......一起来看看 《重构》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具