升级 React 记录
升级 React 版本 记录/文档
Why
React 目前最新的公开版本为 16.7,而 目前项目版本为 15.5。
对比有如下重要变化:
新核心架构:Fiber (16.0)
异步渲染:周期性地对浏览器执行调度渲染工作的策略。 通过异步渲染,避免阻塞了主线程,实施更快地响应。
异步渲染能够将渲染任务划分为多块,这意味着几乎所有的行为都是同步发生的。 React 16.X 使用浏览器提供的 API 间歇性地检查当前是否还有其他任务需要完成,从而实现了对主线程和渲染过程的间接管理。 例如拖动、onChange等在不考虑防抖情况,以及频繁setState的场景,相对于之前版本,有一定性能的提升。
这意味着 React 可以在更细的粒度上控制组件的绘制过程,从最终的用户体验来讲,用户可以体验到更流畅交互及动画体验。而因为异步渲染涉及到 React 的方方面面甚至未来,在 16.0 版本中 React 还暂时没有启用
GitHub - acdlite/react-fiber-architecture: A description of React’s new core algorithm, React Fiber New Core Algorithm · Issue #6170 · facebook/react · GitHub
减少文件体积 (16.0)
React 16 对比 15.6.1
react 从20.7kb(gzip 后:6.9 kb)减至大小为 5.3 kb(gzip 后:2.2 kb)。
react-dom 从141 kb(gzip 后:42.9 kb)减至 103.7 kb(gzip 后:32.6 kb)。
react + react-dom 从 161.7 kb(gzip 后:49.8 kb)减至 109 kb(gzip 后:34.8 kb)
更好地 SSR (16.0)
What’s New With Server-Side Rendering in React 16 – Hacker Noon
Fragment (16.2)
我们编写组件常见模式是将一个组件返回多个元素。
为了包裹多个元素肯定写过的 div 或 span,为了不必要的嵌套,提出了 Fragment
Fragments 与 Vue.js 的 <template> 功能类似,做不可见的包裹元素。
Fragments简写形式 <></>
新版 React 还支持直接返回数组
New LifeCycle (16.3)
官方团队在实现更好地SSR时发现有些现有的生命周期经常被开发者误用或者“巧妙”的使用,这些生命周期容易带来不好的实践。它们是:
-
componentWillMount -
componentWillReceiveProps -
componentWillUpdate
这三个生命周期将在 17.0 版本之前,启用 Strict Mode 下 console 警告⚠️
并在17.0之后,必须加前缀 UNSAFE_ 才能正常使用。
未来版本将逐步去掉这三个生命周期。
React 16.3 一并带来了 两个新的生命周期:
-
static getDerivedStateFromProps -
getSnapshotBeforeUpdate
class Example extends React.Component {
static getDerivedStateFromProps(props, state) {
// ...
}
}
-
static getDerivedStateFromProps返回要更新的 state 内容,返回 null表示没有 state 需要更新(为差异化更新 state,与现有 setState 方式一致;与 React Hooks 方式不同)
class Example extends React.Component {
getSnapshotBeforeUpdate(prevProps, prevState) {
// ...
}
}
New Context API (16.3)
现有 Context API 一直被官方定义为实验性API,有许多问题:
-
违反目前React组件设计。无法将使用 context 的子组件无缝移植到其他的根组件树种
-
在 Context 值更新后,顶层组件向目标组件 的 props 透传过程中,如果中间过程的某个组件的
shouldComponentUpdate返回了 false,所以无法触发之后子组件的 reRender,导致无法得到新的 Context 值
而 新的 Context API 解决了以上的问题,并且采用声明式写法。小型复杂组件间适合采用 Context
React.memo (16.6)
类似于 Class 组件使用的 PureComponent
这是为 functional component 实现的过滤层,只有在 props 变化(浅比较)才会更新组件。
const MyComponent = React.memo(function MyComponent(props) {
// XXX
});
Lazy (16.6)
Lazy
const OtherComponent = React.lazy(() => import("./OtherComponent"));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
Suspense 用来添加一个 placeholder,在 lazy 化 component 加载之前显示
fallback 是懒加载组件载入过程中的一个过渡,可以放一些过渡效果或方法。
目前不能SSR使用
const OtherComponent = React.lazy(() => import("./OtherComponent"));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</section>
</Suspense>
</div>
);
}
Strict Mode (16.6)
Hooks
重点!
好吧,目前 16.7 还不能用,但未来几个月就可以用上这一突破功能。
有关文档:Introducing Hooks – React
这次升级 React 的很大部分因素是 为了能使用到 Hooks
How
简要记录升级过程
升级 react 包
选择升级的 npm 包 react react-dom 以及重点选择了几个有关react的包
推荐使用
npm-check去选择需要更新的包
替换生命周期
总览: LifeCycle 变更
- 未来将去除
-
componentWillMount -
componentWillUpdate -
componentWillReceiveProps
-
- 新生命周期
-
static getDerivedStateFromProps -
getSnapshotBeforeUpdate
-
实施
-
componentWillMount——>componentDidMount无痛变更componentWillMount存在的 问题:- componentWillMount 中 setState 一定会等到首次render 之后才执行reRender
- SSR 会 call componentWillMount twice
- 组件已 mounted,组件设计原则
- Fiber 架构 导致 调用 componentWillMount 次数不确定
-
componentWillUpdate——>componentDidUpdate无痛变更 与componentWillMount存在的问题差不多,也可能会多次执行 -
componentWillReceiveProps ——> static getDerivedStateFromProps + componentDidUpdate
这个可能是修改起来比较麻烦的一个了,项目中使用的机会比较多,一般用作:
- 根据
Props变更与否来修改State -
Props改变时去调用一些Func,或者做出一些不会修改state的操作
与
componentWillReceiveProps不同,getDerivedStateFromProps在 Mounting 阶段也会执行
针对第一种,更换方法:
componentWillReceiveProps ——> getDerivedStateFromProp
static getDerivedStateFromProps(nextProps, prevState) {
// ...
}
注意这是一个
static function,也就是说,在内部使用this是指向类的,而不是实例,所以this并不是指向实例,意味着无法使用this.props/this.state
返回需要更新的state(差异化更新,与现有 setState 方式一致,与 Hooks 不同) 或是 null :表示state不需要更新
// before
componentWillReceiveProps(nextProps) {
if (this.props.currentRow !== nextProps.currentRow) {
this.setState({
isScrollingDown: nextProps.currentRow > this.props.currentRow
})
}
}
// after
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.currentRow !== prevState.lastRow) {
return {
isScrollingDown: nextProps.currentRow > prevState.lastRow,
lastRow: nextProps.currentRow
}
}
// Return null to indicate no change to state.
return null
}
通过例子:
以往我们依赖比较 this.props.xxx 与 nextProps.xxx 或者 nextProps.xxx 与 this.state.xxx是不行了。
在 getDerivedStateFromProps 中,需要将要比较的值也存到state当中,才能在之后通过 第二个参数 prevState 进行比较,比如 nextProps.xxx vs prevState.xxx
官方禁止了组件在
getDerivedStateFromProps中 去访问 this.props,强制让开发者去比较nextProps与prevState中的值,以确保当开发者用到getDerivedStateFromProps这个生命周期函数时,就是在根据当前的props来更新组件的state,而不是去做其他一些让组件自身状态变得更加不可预测的事情。
返回值的机制和使用 setState 的机制是类似的 —— 只需要返回发生改变的那部分状态,其他的值会保留。
WHY: 设计
getDerivedStateFromProps 时,不加入一个prevProps参数,以方便比较
- 首次加载,调用 getDerivedStateFromProps 时,prevProps 参数是空值,这就依赖我们后面的代码考虑到这一条件
- 为了释放内存,保留之前的 props 是需要消耗内存的
针对第二种,使用 compomentDidUpdate:
基于 getDerivedStateFromProp 的执行次数不可预测性,以及静态方法。
所有需要执行的 side-effects要放在 compomentDidUpdate 中去执行
基本上在旧有 componentWillReceiveProps 中没有更新 state 的情况下,使用这个 methods 来代替是完全可行的,因为包括了我们需要用的到prevProps,并且 componentDidUpdate 内部可以访问 this.props,利用这两个我们可以作出对应比较
componentDidUpdate(prevProps, prevState) {
if (this.props.reqPending !== prevProps.reqPending && !this.props.reqPending) {
this.props.history.push('/next-page')
}
}
旧方法
componentWillReceiveProps和新getDerivedStateFromProps方法都会增加组件的复杂性 强烈建议用这样派生状态前思考组件的设计模式。 官方建议: You Probably Don’t Need Derived State – React Blog

在 16.4 修复了 16.3 版本关于 getDerivedStateFromProps 调用的时机,加入了 组件 自身触发setState 以及 forceUpate
建议:
- 保证无副作用 的 getDerivedStateFromProps
- 计算受控值时,将传入的 props 与先前更新好的 state 进行比较 React v16.4.0: Pointer Events – React Blog
-
getSnapshotBeforeUpdate很少情况会用到。
将在 DOM 被更新前调用,此生命周期的返回值将作为第三个参数传递给componentDidUpdate
Context 替换
暂无替换,鉴于项目内使用到的地方比较杂,旧 Context API 方法在 17.0 之前还是可以正常使用
Pains
升级遇到的痛点
- 有很多组件在
componentWillReceiveProps异步延时调用 setState …… ,而getDerivedStateFromProps中无法 异步更新state,所以做了大量工作去优化重写,实在优化不了的在DidUpdate去调用setState(会造成多次渲染,但组件树不复杂的情况,忽略这些开销)
Dan 回复过一个有关问题 : getDerivedStateFromProps for asynchronous setState · Issue #1147 · reactjs/reactjs.org · GitHub
javascript - How to use React’s getDerivedStateFromProps with a setTimeout? - Stack Overflow
-
有很多子组件
state初始值依赖于父组件,但却是在componentWillReceiveProps方法中 去初始化,之后子组件setState替换掉state值。由于子组件setState不会调用componentWillReceiveProps,但会调用getDerivedStateFromProps,导致更新state值无效。 解决:修改逻辑,加状态锁 -
之前很多逻辑只依赖于
componentWillReceiveProps,现在用getDerivedStateFromProps同componentDidUpdate去替换,导致逻辑分层,有些相似的逻辑要重复写(并不是说这里可以写个方法来重用,只是多了很多不必要的相似逻辑)
期待未来版本
Hooks将其统一。
-
有些组件的逻辑 严重依赖
componentWillReceiveProps,而getDerivedStateFromProps在mounting阶段也会调用,需要根据具体情况修改逻辑… -
basicPopup组件…… 此组件在submodule,由于修改此组件,需要配合修改所有继承自它的Popup组件,而 这些组件并不全部在teachingSite。也就是说,如果修改它,必须将依赖于它的所有项目升级 react 版本至 16.4 及其以上。暂时放弃此组件及其子类组件。(备注:在17.0之前,过去的生命周期可正常使用)
Regret
- basicPopup 及其子类组件还是有 未来将删去的
componentWillReceiveProps - Context 目前还没有变更至新 Context
np
@hongzzz
昨天还看到了你的 safeSetState
:cow::beer:
np
np