“组件”和 “虚拟 DOM” 是 React 基本原理中极为关键的两个概念,是理解生命周期设计的切入点
虚拟 DOM:核心算法的基石
组件化:工程化思想在框架中的落地
组件化是 React 团队在研发交通方面所做的一个重要的努力
几乎所有的可见/不可见的内容都可以被离为各种各样的组件,每个组件即是“封闭”的,也是“开放”的
封装
针对“渲染工作流”来说
-
开放
针对组件间通信来说
React 允许开发者基于“单向数据流” 的原则,完成组件间的通信
而组件之间的通信又将改变通信双方/某一方内部的数据进而对渲染结果构成影响
生命周期方法的本质:组件的“灵魂”与“躯干”
将 render 方法形容为 React 组件的“灵魂”
“渲染工作流”
从组件数据改变到组件实际更新发生的过程
render 之外的生命周期方法可以理解为是组件的“躯干”
“躯干”未必总是会做具体的整改,倘若“躯干”做了点什么,往往都会直接或间接地影响到“灵魂”
拆解 React 生命周期
认识 React15 的生命周期流程
Mounting 阶段:组件的初始化渲染(挂载)
render 在执行过程中并不会去操作真实 DOM,它职能是把需要渲染的内容返回出来
- componentDidMount 方法在渲染结束后被触发
真实 DOM 已经持载到页面上,可以在这个生命周期里执行真实 DOM 相关的操作Updating 阶段:组件的更新
组件的更新分为两种
- 由父组件更新触发的更新
- 由组件自身调用 setState 方法触发的更新
父组件更新与自身组件更新多出了 componentWillReceiveProps 的生命周期方法
如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法 (ComponentReceiveProps)。如果只想处理更改,请确保进行当前值与变更值的比较。 —— React 官方
componentReceiveProps 并不是由 props 的变化触发的,而是由父组件的更新触发的
渲染性能优化
React 组件会根据 shouldComponentUpdate 的返回值来决定是否执行该方法之后的生命周期,进而决定是滞对组件进行 re-render(重渲染)
Unmounting 阶段:组件的卸载
componentWillUnmount 的触发
- 组件在父组件被移除了
- 组件中设置了 key 属性
父经胗的在 render 的过程中,发现 key 值和上一次不一致进化的生命周期方法:React16 生命周期工作流
Mounting 阶段:组件的初始化渲染(挂载)
消失的 componentWillMount,新增的 getDerivedStateFromProps
废弃了 componentWillMount,新增了 getDerivedStateFromProps注:React16 对 render 方法也进行了一些改进。React16 之前,render 方法必须返回单个元素,而 React16 允许返回元素数组和字符串。
getDerivedStateFromProps 并不是 componentWillMount 的替代品
- componentWillMount 的存在不仅“鸡肋”而且危险,因此它并不值得被“代替”,它就应该被废弃
- getDerivedStateFromProps 有且仅有一个用途:
使用 props 来派生/更新 state- React 团队直接从命名层面约束它的用途
- getDerivedStateFrom 在更新和挂载两个阶段都会“出镜”,挂载阶段的生命周期改变是一个雄心勃勃的“进化”逻辑
- getDerivedStateFromProps 是一个静态方法(不能使用 this)
- 该方法可以接收两个参数:props 和 state
- getDerivedStateFromProps 需要一个对象格式的返回值,如果不需要此行为要么不写这个生命周期或者返回 null
getDerivedStateFromProps 方法对 state 的更新动作并非“覆盖”式的更新,而是针对某个属性的定向更新(merge update)
Updating 阶段:组件的更新
React16.4 后更新流程与 React16.3 的微调就在于 getDerivedStateFromProps
React16.4 任何因素触发的更新流程都会触发 getDerivedStateFromProps ,而在 React16.3 只有在父组件的更新才会触发 getDerivedStateFromProps 的生命周期为什么要用 getDerivedStateFromProps 代替 componentWillReceiveProps?
与 componentDidUpdate 一起,这个新的生命周期涵盖过时 componetWilReceiveProps 的所有用例。 —— React 官方
getDerivedStateFromProps 是作为一个试图代替 componentWillReceiveProps 的 API 而出现
- getDerivedStateFromProps 可以代替 componentWillReceiveProps 是实现基于 props 派生 state
- getDerivedStateFromProps 不能完全和 componentWillReceiveProps 画等号
- 原则上来说 getDerivedStateFromProps 能做且只能做这一件事
一个 API 并非越庞大越复杂才越优秀,庞大复杂的 API 往往带来维护的灾难。
getDerivedStateFromProps 这个 API 正是做了“合理的减法”
- 从 getDerivedStateFromProps 直接被定义为 static 方法这件事上就可以一班,在拿不到 this 就可以防止做 this.fetch / 不合理的 this.setState 这类会产生副作用的操作
- React16 在强行推行“只用 getDerivedStateFromProps 来完成 props 到 state 的映射”这一最佳实践,
一再确保生命周期函数的行为更加可控可预测,在根源避免开发者不合理的开发模式- 避免生命周期的滥用
- 同时为新的 Fiber 架构铺路
消失的 componentWillUpdate,新增的 getSnapshotBeforeUpdate
getSnapshotBeforeUpdate 的返回值会作为第三个参数给到 componentDidUpdate ,它的执行时机是在 render 方法之后,真实 DOM 更新之前,同时获取到更新前的真实 DOM 和更新前后的 state & props 信息
实现一个内容会发生变化的滚动列表,要求根据滚动列表的内容是否发生变化来决定是否要记录滚动条的当前位置
getSnapshotBeforeUpdate 要想发挥作用离不开 componentDidUpdate 的配合
Unmounting 阶段:组件的卸载
React16 缘何两次求变?
Fiber 是 React16 对 React 核心算法的一次重写,会使用原本同步的渲染过程变成异步的
Fiber 会将一个大的更新任务拆解为许多小任务
Fiber 架框的重要特征就是可以被打断的异步渲染模式,根据“能否被打断”这一标准,React16 的生命周期被划分为 render 和 commit 两个阶段,commit 又细分为 pre-commit 和 commit
render 阶段在执行过程中允许被打断,而 commit 阶段则总是同步执行
因为 render 阶段是用户不可以见的就是打断重启用户也是零感知,而 commit 阶段操作是涉及真实 DOM 的渲染必需同步
render 阶段是允许暂停、终止和重启
这导致 render 阶段生命周期都是有可能被重复执行的
- componentWillMount
- componentWillUpdate
- componentWillReceiveProps
它们都处于 render 阶段,都可能重复被执行;它们在生命周期中都可能存在不可少缺的风险:
- 完全可以转移到其它生命周期(尤其是 componentDidxxx)里去做
异步请求再怎样快也快不过(React15 下)同步的生命周期,首次渲染依然会在数据返回之前的执行 - 在 Fiber 带来的异步渲染机制下可能会惬以非常严重的 Bug
由于 render 阶段里的生命周期都可以重复执行,在 componentWillxxx 被打断 + 重启多次后就会发出多个付款请求
所以 getDrivedStateFromProps 被设计为 static 方法是避免开发者触碰 this,就是在避免各种危险的骚操作 - 即使没有开启异步,React15 下也有不少人能把自己“玩死”,如重复渲染造成死循环
React16 改造生命周期的主要动机
是为了配合 Fiber 架构带来的异步渲染机制,针对生命周期中长期被滥用的部分推行了具有强制性的最佳实践
这一工作下来,确保了 Fiber 机制下数据和视图的安全性,同时也确保了生命周期方法的行为更加纯粹、可控、可预测