React新旧版本生命周期变化
由于一些生命周期函数经常会被误解和滥用,同时
React
官方也为了实现后面新版本的异步渲染,废除了一些生命周期函数,同时也新增了一些生命周期函数
新旧生命周期版本对比
v16.3之前的生命周期图谱:
v16.3版本的生命周期图谱:
v16.4版本的生命周期只是改动了getDerivedStateFromProps()的调用情形, 只要是可能会触发组件更新的情况(New props, setState(), fourceUpdate()),就会调用该生命周期函数。
弃用的生命周期函数(UNSAFE)
1. componentWillMount()
一般开发者在这里常做三件事:初始化state、调用异步接口获取数据、订阅事件/添加事件监听器
该生命周期方法经常被误解和滥用;我们对它有哪些认知上的误解呢?
componentWillMount
与componentWillUnMount
并不是一定会一前一后成对出现的,不意味着同一组件内前者调用之后,后者一定会被调用。事实上,React会确保在成功调用componentDidMount()
生命周期函数之后,随后才会调用componentWillUnMount
进行内存清除相关操作 (内存泄漏)
componentWillMount
执行之后会立即调用render()
函数,并不能保证在用户看到UI之前使那些调用异步接口的数据返回,也就不能解决初次渲染时空数据的问题。
由于React
未来会推出新的渲染方式--异步渲染,一种生命周期可被打断的渲染方式,具体是在render()
生成虚拟 dom
阶段可以打断重来, 这就会导致在dom被挂在之前或是被更新之前的所有任务都会重复操作,所以componentWillMount()
、·componentWillReceiveProps()
componentWIllUpdate()
方法可能会执行多次。(函数内部逻辑多次调用)
该方法是服务端渲染唯一一个会调用的生命周期函数。(服务端渲染时在这里声明的变量可能无法清除,内存泄漏)
基于以上,使用'componentWillMount()'会造成内存泄漏, 数据重复加载等问题;新版本中官方推荐将初始化的操作放在constructor()
中, 将请求异步数据、订阅事件源、监听事件的操作放在componentDidMount()
2.componentWillReceiveProps(nextProps, nextContext)
在老版本的React
中,如果组件自身的state与其props密切相关的话,我们就会用到componentWillReceiveProps(nextProps)
。常见的业务场景比如,tabs的激活状态,一般我们会在组件自身内通过state维持,但是当我们从其他页面返回时,想要保持离开之前时的tabs状态,这时我们可以通过props来传递,(破坏了数据源的单一性)
//previous
componentWillReceiveProps(nextProps, nextContext) {
if(nextProps.activeIndex !== this.state.activeIndex) {
this.setState({activeIndex: nextProps.activeIndex})
this.fetchData() //因异步中断,可能会重复操作
}
}
/** next
*将更新state与触发逻辑的操作分成两部分来执行,state更新部分在getDerivedStateFromProps中完成, 逻辑部分操作
*在componentDidUpdate()中完成
*/
static getDerivedStateFromProps(nextProps, prevState) { //此方法不能获取组件实例
if(nextProps.activeIndex !== prevState.activeIndex) {
return {activeIndex: nextProps.activeIndex}
}
}
componentDidUpdate() {
this.fetchData() //可以确保只执行一次
}
该生命周期函数按照上面图谱中应该是在props属性改变之后调用,但其实只要父组件重新渲染,无论子组件的props有没有更新,子组件都会调用componentWillReceiveProps
注意这里可能会造成死循环,即当子组件在该方法中调用了父组件通过props传递过来的函数时, 恰巧该函数中有能让父组件重新渲染的逻辑,就会造成死循环
class Parent extends Component {
render() {
return (
<div>
{/* 迫使父组件更新, 子组件就会调用componentWillReceiveProps */}
<div onClick={() => this.forceUpdate()}> re-render </div>
<Child parentFun={() => this.setState({})} />
</div>
)
}
}
class Child extends Component {
componentWillReceiveProps(nextProps, nextContext) {
{/* 该函数也会使父组件重新渲染,造成死循环 */}
nextProps.parentFun()
}
render() {
return (
<div> child component </div>
);
}
}
基于以上,使用componentWillReceiveProps()
会照成方法多次调用React
提出 static getDerivedStateFromProps()
替代方案.
3.componentWillUpdate(nextProps, nextState)
该方法发生在组件渲染之前,初次渲染时不会调用
我们通常的认识是:由于该方法在render()
之前调用, 如果把数据更新的操作放在componentDidUpdate()
中就来不及了,因为该方法在render()
之后调用嘛。但事实并非如此,React会确保用户在看到最新的UI之前,刷新componentWillUpdate()
和componentDidUpdate()
期间发生的任何setState()
调用,注意最好不要在这里直接进行setState的操作否则会导致进入死循环。
在组件更新前读取DOM元素状态
另一个操作就是在组件更新前获取dom元素状态,更新之后在componentDidUpdate
中处理。但是此方法在render
之前调用,当React开启异步渲染模式后,render
之前的方法都可能会被多次调用,而且也不能够保证在这里获取的DOM元素状态是最近一次的。
新增的生命周期函数
1.static getDerivedStateFromProps(nextProps, prevState)
这是一个不会被子类继承的静态函数。会在组件每次渲染之前调用,该方法接收此次props,和当前还没更新state作为参数,返回一个对象用于更新state,如果返回null则不更新任何内容。该方法不可以获取组件实例this, React官方本身的目的也是为了让开发者认识到,但凡使用该方法就是为了处理组件内部state与props相关联的这一类问题。
2. getSnapshotBeforeUpdate(preProps, preState)
该函数发生在componentDidUpdate
之前, 可以获取组件在更新之前且是最近一次的DOM
元素状态, 返回一个更新前的快照snapshot
,作为componentDidUpdate
的第三个参数。这个生命周期函数不经常用到, 这里举一个实际中一般不会这么用的场景,仅是为了加深对该生命周期函数的理解
//需求: 每次调用fetchData时会加载数据,要求在数据每次加载后,页面能自动滚动到最新数据处
fetchData() {
this.setState({count: this.state.count + 1})
let arr = [1,2,3,4,5]
this.setState({list: [...this.state.list, ...arr]})
}
getSnapshotBeforeUpdate(preProps, preState) {
if(this.state.list.length > preState.list.length) {
const current = document.getElementById("current")
//获取上一帧内容的可见高度
return current.scrollHeight - current.scrollTop
} else return null
}
componentDidUpdate(preProps, preState, snapshort) {
const current = document.getElementById("current")
if(snapshort) {
// 如果有新数据时,向上滚动直至完全隐藏上一帧内容可见的高度
current.scrollTop = current.scrollHeight - snapshort
}
}
总结
React
将要废弃的这三个生命周期函数,大都是因为新版本的异步渲染,在调用render
生成虚拟DOM阶段,由于更高级的操作到来而被打断,导致render
之前的操作都会重来。使得之前的逻辑可能会被重复调用而弃用。