React V16 生命周期函数调整前后的异同:
before:
after:
1.constructor
constructor参数接受两个参数props,context
可以获取到父组件传下来的的props,context,如果你想在constructor构造函数内部(注意是内部哦,在组件其他地方是可以直接接收的)使用props或context,则需要传入,并传入super对象。
constructor(props,context) {
super(props,context)
console.log(this.props,this.context) // 在内部可以使用props和context
}
当然如果你只需要在构造函数内使用props或者context,那么只传入一个参数即可,如果都不可以,就都不传。
constructor中应当做些初始化的动作,如:初始化state,将事件处理函数绑定到类实例上,但也不要使用setState()。如果没有必要初始化state或绑定方法,则不需要构造constructor,或者把这个组件换成纯函数写法。
关于ES6的class constructor和super
只要组件存在constructor,就必要要写super,否则this指向会错误
constructor() {
console.log(this) // 报错,this指向错误
}
2.生 命 周 期
(1)初始化
<1>、getDefaultProps()
设置默认的props,也可以用dufaultProps设置组件的默认属性.
<2>、getInitialState()
在使用es6的class语法时是没有这个钩子函数的,可以直接在constructor中定义this.state。此时可以访问this.props
<3>、componentWillMount()(react v17.0 中将移除)
组件初始化时调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。在这个方法中调用setState()不会起作用,是由于他在render()前被调用。为了避免副作用和其他的订阅,官方都建议使用componentDidMount()代替。这个方法是用于在服务器渲染上的唯一方法。
首屏无数据导致白屏
避免第一次渲染时页面因为没有获取到异步数据导致的白屏,而将数据请求部分的代码放在了 componentWillMount 中,希望可以避免白屏并提早异步请求的发送时间。但事实上在 componentWillMount 执行后,第一次渲染就已经开始了,所以如果在 componentWillMount 执行时还没有获取到异步数据的话,页面首次渲染时也仍然会处于没有异步数据的状态。换句话说,组件在首次渲染时总是会处于没有异步数据的状态,所以不论在哪里发送数据请求,都无法直接解决这一问题。而关于提早发送数据请求,官方也鼓励将数据请求部分的代码放在组件的 constructor 中,而不是 componentWillMount。
事件订阅
另一个常见的用例是在 componentWillMount 中订阅事件,并在 componentWillUnmount 中取消掉相应的事件订阅。但事实上 React 并不能够保证在 componentWillMount 被调用后,同一组件的 componentWillUnmount 也一定会被调用。一个当前版本的例子如服务端渲染时,componentWillUnmount 是不会在服务端被调用的,所以在 componentWillMount 中订阅事件就会直接导致服务端的内存泄漏。另一方面,在未来 React 开启异步渲染模式后,在 componentWillMount 被调用之后,组件的渲染也很有可能会被其他的事务所打断,导致 componentWillUnmount 不会被调用。而 componentDidMount 就不存在这个问题,在 componentDidMount 被调用后,componentWillUnmount 一定会随后被调用到,并根据具体代码清除掉组件中存在的事件订阅。
<4>、 render()
react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。
返回的类型有以下几种:
原生的DOM,如div
React组件
Fragment(片段)
Portals(插槽)
字符串和数字,被渲染成text节点
Boolean和null,不会渲染任何东西
<5>、componentDidMount()
组件渲染之后调用,只调用一次。组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染。
DOM元素(React Native的原生元素)可以在这个方法里取到。这时可以在这个方法里执行数据获取等操作。如果需要的话,任何的DOM操作都可以在这里执行,绝对不可以在render方法里执行。
(2)更新
<6>、componentWillReceiveProps(nextProps)(react v17.0 中将移除)
组件初始化时不调用,组件接受新的props时调用。在接受父组件改变后的props需要重新渲染组件时用到的比较多。它接受一个参数 nextProps,通过对比nextProps和this.props,将nextProps setState为当前组件的state,从而重新渲染组件。
<7>、shouldComponentUpdate(nextProps, nextState)
react性能优化非常重要的一环。唯一用于控制组件重新渲染的生命周期。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候。
因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断。
返回false不会阻止子组件在state更改时重新渲染。官方并不建议在shouldComponentUpdate()中进行深度查询或使用JSON.stringify(),他效率非常低,并且损伤性能。
<8>、componentWillUpdate(nextProps, nextState)(react v17.0 中将移除)
组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state。比如 nextProps.name = '新名字', render 的时候就会用到更改后的值。不能直接调用this.setState(), 这样会触发shouldComponentUpdate(), 从而进入死循环。shouldComponentUpdate返回true以后,组件进入重新渲染的流程。
与 componentWillReceiveProps 类似,许多开发者也会在 componentWillUpdate 中根据 props 的变化去触发一些回调。但不论是 componentWillReceiveProps 还是 componentWillUpdate,都有可能在一次更新中被调用多次,也就是说写在这里的回调函数也有可能会被调用多次,这显然是不可取的。与 componentDidMount 类似,一次更新中 componentDidUpdate 只会被调用一次,所以将原先写在 componentWillUpdate 中的回调迁移至 componentDidUpdate 就可以解决这个问题。
<9>、render()
组件渲染。render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。react16中 render函数允许返回一个数组,单个字符串等,不在只限制为一个顶级DOM节点,可以减少很多不必要的div。
如:(不要忘记给每个数组元素添加key,防止出现警告)
换一种写法,可以不写key(v16++)
<10>、componentDidUpdate(prevProps, prevState, snapshot)
组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点,可以执行DOM操作,这也是一个适合发送请求的地方。组件更新完毕后,react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state。
(3)卸载
<11>、componentWillUnmount()
组件将要卸载时调用,一些事件监听和定时器需要在此时清除。
1.clear你在组建中所有的setTimeout,setInterval
2.移除所有组建中的监听 removeEventListener
3.也许你会经常遇到这个warning
(4) react v16 新增生命周期
<12>、 getDerivedStateFromProps(nextProps, prevState)
如果是由于父组件的props更改,所带来的重新渲染,也会触发此方法。需要注意,这个方法是个 static 的方法,因此使用 this 在这个方法中并不指代本组件,如果打印出来会发现这个this在这个方法中是null。而且这个方法会返回值。 当需要更新状态时,需要返回一个 object ,如果不需要任何更新,则返回null即可。如果只想要处理更新的话,最好加上判断条件if (nextProp !== prevProp)。
调用setState()不会触发getDerivedStateFromProps()。
通常来讲,在 componentWillReceiveProps 中,我们一般会做以下两件事,一是根据 props 来更新 state,二是触发一些回调,如动画或页面跳转等。在老版本的 React 中,这两件事我们都需要在 componentWillReceiveProps 中去做。而在新版本中,官方将更新 state 与触发回调重新分配到了 getDerivedStateFromProps 与 componentDidUpdate 中,使得组件整体的更新逻辑更为清晰。而且在 getDerivedStateFromProps 中还禁止了组件去访问 this.props,强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去做其他一些让组件自身状态变得更加不可预测的事情。
<13>.getSnapshotBeforeUpdate(prevProps, prevState)
与 componentWillUpdate 不同,getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。虽然 getSnapshotBeforeUpdate 不是一个静态方法,但我们也应该尽量使用它去返回一个值。这个值会随后被传入到 componentDidUpdate 中,然后我们就可以在 componentDidUpdate 中去更新组件的状态,而不是在 getSnapshotBeforeUpdate 中直接更新组件状态。
(5)component.forceUpdate(callback) react forceUpdate
默认情况下,当组件的 state 或 props 改变时,组件将重新渲染。 如果你的 render() 方法依赖于一些其他数据,你可以告诉 React 组件需要通过调用 forceUpdate() 重新渲染。
调用 forceUpdate() 会导致组件跳过 shouldComponentUpdate() ,直接调用 render()。 这将触发子组件的正常生命周期方法,包括每个子组件的 shouldComponentUpdate() 方法。
forceUpdate就是重新render。有些变量不在state上,但是你又想达到这个变量更新的时候,刷新render;或者state里的某个变量层次太深,更新的时候没有自动触发render。这些时候都可以手动调用forceUpdate自动触发render。
(6)componentDidCatch 错误处理
错误在渲染阶段中被捕获,但在事件处理程序中不会被捕获。
然后在顶部或任何地方,你可以这样使用它:
另一个令人敬畏的特性 componentDidCatch 是包含错误堆栈的 info 对象!
3. react生命周期父子组件渲染顺序
在react的组件挂载及render过程中,最底层的子组件是最先完成挂载及更新的。
constructor()构造函数、componentWillMount执行顺序:
顶层父组件--子组件--子组件--...--底层子组件
render、componentDidMount顺序:
底层子组件--子组件--子组件--...--顶层父组件
update phases同理
作者:animasling
链接:https://www.jianshu.com/p/c5f5984e42c3