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

不知道小伙伴有没有注意到,自从react更新到16.3版本后,之前使用的 componentWillMountcomponentWillReceivePropscomponentWillUpdate三个生命周期函数都有eslint报警,让咱们使用 UNSAFE_前缀的新的生命周期函数。不由有疑问“react这是意欲何为啊”?为何要加 UNSAFE_前缀?

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

一、componentWillMountreact

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生命周期会在组件移除的时候被执行,避免内存泄漏的风险。bash

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

二、componentWillReceivePropsasync

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

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

...
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生命周期函数执行。如此就会陷入死循环。致使程序崩溃。this

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

三、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。下面会有具体说明,这里暂时卖个关子。

有什么替换方案?

一、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

二、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_”生命周期名称将起做用。)

结论

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

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

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

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

相关文章
相关标签/搜索