React 生命周期很多人都了解,但通常我们所了解的都是 单个组件 的生命周期,但针对 Hooks 组件、多个关联组件(父子组件和兄弟组件) 的生命周期又是怎么样的喃?你有思考和了解过吗,接下来我们将完整的了解 React 生命周期。
关于 组件 ,我们这里指的是 React.Component
以及 React.PureComponent
,但是否包括 Hooks 组件喃?
一、Hooks 组件
函数组件 的本质是函数,没有 state 的概念的,因此不存在生命周期一说,仅仅是一个 render 函数而已。
但是引入 Hooks 之后就变得不同了,它能让组件在不使用 class 的情况下使用 state 以及其他的 React特性,相比与 class 的生命周期概念来说,它更接近于实现状态同步,而不是响应生命周期事件。但我们可以利用 useState
、 useEffect()
和 useLayoutEffect()
来模拟实现生命周期
即:Hooks 组件更接近于实现状态同步,而不是响应生命周期事件
下面,是具体的 生命周期 与 Hooks 的对应关系:
constructor
:函数组件不需要构造函数,我们可以通过调用 useState
来初始化 state。如果计算的代价比较昂贵,也可以传一个函数给 useState
。
const [num, UpdateNum] = useState(0)
复制代码
const [num, UpdateNum] = useState(0)
复制代码
getDerivedStateFromProps
:一般情况下,我们不需要使用它,我们可以在渲染过程中更新 state,以达到实现 getDerivedStateFromProps
的目的。
function ScrollView({row}) {
let [isScrollingDown, setIsScrollingDown] = useState(false);
let [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// Row 自上次渲染以来发生过改变。更新 isScrollingDown。
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
function ScrollView({row}) {
let [isScrollingDown, setIsScrollingDown] = useState(false);
let [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// Row 自上次渲染以来发生过改变。更新 isScrollingDown。
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
对应表格
为方便记忆,大致汇总成表格如下。
class 组件 | Hooks 组件 |
---|---|
constructor | useState |
getDerivedStateFromProps | useState 里面 update 函数 |
shouldComponentUpdate | useMemo |
render | 函数本身 |
componentDidMount | useEffect |
componentDidUpdate | useEffect |
componentWillUnmount | useEffect 里面返回的函数 |
componentDidCatch | 无 |
getDerivedStateFromError | 无 |
多个组件的执行顺序
1. 父子组件
挂载阶段
分 两个 阶段:
- 第 一 阶段,由父组件开始执行到自身的
render
,解析其下有哪些子组件需要渲染,并对其中 同步的子组件 进行创建,按 递归顺序 挨个执行各个子组件至render
,生成到父子组件对应的 Virtual DOM 树,并 commit 到 DOM。 - 第 二 阶段,此时 DOM 节点已经生成完毕,组件挂载完成,开始后续流程。先依次触发同步子组件各自的
componentDidMount
,最后触发父组件的。
注意:如果父组件中包含异步子组件,则会在父组件挂载完成后被创建。
所以执行顺序是:
父组件 getDerivedStateFromProps —> 同步子组件 getDerivedStateFromProps —> 同步子组件 componentDidMount —> 父组件 componentDidMount —> 异步子组件 getDerivedStateFromProps —> 异步子组件 componentDidMount
- 第 一 阶段,由父组件开始执行到自身的
更新阶段
React 的设计遵循单向数据流模型 ,也就是说,数据均是由父组件流向子组件。
第 一 阶段,由父组件开始,执行
static getDerivedStateFromProps
shouldComponentUpdate
更新到自身的
render
,解析其下有哪些子组件需要渲染,并对 子组件 进行创建,按 递归顺序 挨个执行各个子组件至render
,生成到父子组件对应的 Virtual DOM 树,并与已有的 Virtual DOM 树 比较,计算出 Virtual DOM 真正变化的部分 ,并只针对该部分进行的原生DOM操作。第 二 阶段,此时 DOM 节点已经生成完毕,组件挂载完成,开始后续流程。先依次触发同步子组件以下函数,最后触发父组件的。
getSnapshotBeforeUpdate()
componentDidUpdate()
React 会按照上面的顺序依次执行这些函数,每个函数都是各个子组件的先执行,然后才是父组件的执行。
所以执行顺序是:
父组件 getDerivedStateFromProps —> 父组件 shouldComponentUpdate —> 子组件 getDerivedStateFromProps —> 子组件 shouldComponentUpdate —> 子组件 getSnapshotBeforeUpdate —> 父组件 getSnapshotBeforeUpdate —> 子组件 componentDidUpdate —> 父组件 componentDidUpdate
卸载阶段
componentWillUnmount()
,顺序为 父组件的先执行,子组件按照在 JSX 中定义的顺序依次执行各自的方法。注意 :如果卸载旧组件的同时伴随有新组件的创建,新组件会先被创建并执行完
render
,然后卸载不需要的旧组件,最后新组件执行挂载完成的回调。
2. 兄弟组件
挂载阶段
若是同步路由,它们的创建顺序和其在共同父组件中定义的先后顺序是 一致 的。
若是异步路由,它们的创建顺序和 js 加载完成的顺序一致。
更新阶段、卸载阶段
兄弟节点之间的通信主要是经过父组件(Redux 和 Context 也是通过改变父组件传递下来的
props
实现的),满足React 的设计遵循单向数据流模型, 因此任何两个组件之间的通信,本质上都可以归结为父子组件更新的情况 。所以,兄弟组件更新、卸载阶段,请参考 父子组件。
Effect 进行性能优化