# 组件
封闭
在组件自身的渲染工作流中,每个组件都只处理它内部的渲染逻辑。
开放
React 允许开发者基于 “单向数据流” 的原则完成组件之间的通信,而组件之间的通信又将改变通信双方/某一方内部的数据,进而对渲染结果构成影响。
# React 15 生命周期
在组件更新中,如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法(componentReceiveProps)。如果只想处理更改,请确保进行当前值与变更值的比较。
—— React官方
componentReceiveProps
不是由 props 的变化触发的,而是由父组件的更新触发的。React 组件会根据
shouldComponentUpdate
的返回值来决定是否执行该方法之后的生命周期,进而决定是否对组件进行 re-render(重渲染)。shouldComponentUpdate
默认为true
,表示无条件 re-render。componentWillUnmount
有两种触发方式:- 组件在父组件中被移除了
- 组件中设置了
key
属性,父组件在 render 过程中,发现key
不一致
# React 16 生命周期
getDerivedStateFromProps
设计的初衷不是为了替换 componentWillMount
,而是为了替换 componentWillReceiveProps
,所以 getDerivedStateFromProps
有且仅有一个用途:使用 props 来派生/更新 state。 并且在挂载和更新时都用到了它。
# getDerivedStateFromProps
- 它是一个静态方法,静态方法不依赖组件实例而存在,所以在该方法内部是访问不到
this
的。 - 该方法接收两个参数:props 和 state。props 表示当前组件接收到的父组件 props,state表示当前组件自身的 state。
- 它需要一个对象形式的返回值,如果没有会被警告。React 需要该返回值来更新组件的 state,所以如果没有的话,至少需要返回一个
null
。
在 React 16.4+,任何原因触发的组件更新,都会触发 getDerivedStateFromProps
,而在 React 16.3,只有父组件更新才会触发 getDerivedStateFromProps
。
# 为什么要用 getDerivedStateFromProps 来代替 componentWillReceiveProps ?
原则上来说,原来的 componentWillReceiveRrops
可以做很多事,而 getDerivedStateFromProps
能做且只能做一件事,就是基于 props 派生 state,这一点在它的定义上也可以看出来(使用了 static
):
class Name extends React.Component {
static getDerivedStateFromProps(props, state){
}
}
2
3
4
static
限制了 getDerivedStateFromProps
里拿不到 this
,也就意味着无法在该方法里做类似 setState
等可能会产生副作用的操作。
所以,React 16 在强制推行:“只用 getDerivedStateFromProps 来完成 props 到 state 的映射”。 在开发方式上避免了开发者对生命周期的滥用,同时也在为 Fiber
架构铺路。
# getSnapshotBeforeUpdate
getSnapshotBeforeUpdate
和 getDerivedStateFromProps
有点类似,它的返回值会作为第三个参数,给到 componentDidUpdate
,它的执行时机是在 render 方法后,真实 DOM 更新之前。同时获取到更新前的真实 DOM 和更新前后的 state 和 props 信息。
class Name extends React.Component {
// 组件更新时调用
getSnapshotBeforeUpdate(prevProps, prevState){
console.log('aaaa')
return 'hhh'
}
// 组件更新后调用
componentDidUpdate(prevProps, prevState, valueFromSnapshot){
console.log('bbb')
console.log('从 getSnapshotBeforeUpdate 传过来的值是', valueFromSnapshot)
}
}
-->
// aaa
// bbb
// 从 getSnapshotBeforeUpdate 传过来的值是 hhh
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
砍掉 componnetWillUpdate
是为了给 Fiber
铺路。
# Fiber 简介
Fiber
是 React 16 对 React 核心算法的一次重写,Fiber
会使原本同步的渲染过程变成异步执行。
Fiber
架构的重要特征就是可以被打断的异步渲染模式,根据 “能否被打断” 这一标准,React 16 的生命周期被划分为了 render 和 commit 两个阶段(commit 又分为 pre-commit 和 commit)。
render 阶段
纯净且不包含副作用,可能会被 React 暂停、终止或重新启动。
pre-commit 阶段
可以读取 DOM。
commit 阶段
可以使用 DOM,运行副作用,安排更新。
当一个任务执行到一半被打断后,下一次渲染线程抢回主动权时,该任务被重启的形式是重复执行一遍整个任务,而不是从打断的那行代码开始执行。这就会导致 render 阶段的生命周期都是有可能被重复执行的。
该阶段对于用户是不可见的,所以打断再启动,对于用户也是无感知的。
再看废弃的 componentWillMount
、componentWillUpdate
、componentWillReceiveProps
,废弃的原因是它们都处于 render 阶段,都可能被重复执行,而且这些 API 都有着被滥用的风险。
在 componentWill
开头的生命周期里,可能会喜欢做:setState、fetch发起请求、操作真实DOM等,这些操作的问题包括但不限于以下三点:
- 这些完全可以转移到其他生命周期(尤其是 componentDidxxx)里去做。异步请求再快也快不过同步的生命周期,componentWillxxx 执行的时候,render 会迅速触发,所以首次渲染依然会在数据返回之前执行(也就是白屏问题)。
- 在 Fiber 的异步渲染机制下,可能会导致非常严重的 Bug。由于 render 阶段的生命周期都可以重复执行,在 componentWillxxx 被 打断/重启 多次后,就会触发多个请求。
- 即使没有开启异步,React 15 下也会有很多人把自己 “玩死”。在
componentWillReceiveProps
和componentWillUpdate
里滥用setState
, 会导致重复渲染死循环。 React 16 改造生命周期的主要动机是为了配合 Fiber 架构带来的异步渲染机制。