# suspense
为什么不推荐在 componentwillmount 里最获取数据的操作呢?
有些朋友可能会想, 数据早点获取回来,页面就能快点渲染出来呀, 提升用户体验, 何乐而为不为?
这个问题, 简单回答起来就是, 因为是可能会调用多次。要深入回答这个问题, 就不得不提到一个React 的核心概念:
React Fiber
- 让大家不要写class的组件,Suspense 能很好的支持函数式组件。
# fiber
React Fiber 是在 v16 的时候引入的一个全新架构, 旨在
解决异步渲染问题。
# 同步渲染和异步渲染
因为JavaScript单线程的特点,每个同步任务不能耗时太长,不然就会让程序不会对其他输入作出相应,React的更新过程就是犯了这个禁忌,而React Fiber就是要改变现状。
- Fiber 的做法是:分片。
把一个很耗时的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。 而维护每一个分片的数据结构, 就是Fiber。
因为一个更新过程可能被打断,所以React Fiber一个更新过程被分为两个阶段: render phase and commit phase.
# render
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
# commit
componentDidMount
componentDidUpdate
componentWillUnmount
首先,一个组件的 componentWillMount 比 componentDidMount 也早调用不了几微秒,性能没啥提高,而且如果开启了异步渲染, 这就难受了。
static getDerivedStateFromProps(nextProps, prevState) {
//根据nextProps和prevState计算出预期的状态改变,返回结果会被送给setState
}
2
3
React v16.3,干脆引入了一个新的生命周期函数 getDerivedStateFromProps, 这个函数是一个 static 函数,也是一个纯函数,里面不能通过 this 访问到当前组件(强制避免一些有副作用的操作),输入只能通过参数,对组件渲染的影响只能通过返回值。目的大概也是让开发者逐步去适应异步渲染。
React v16发布时,还增加了一个
componentDidCatch,当异常发生时,一个可以捕捉到异常的componentDidCatch就排上用场了如果异常发生在
render阶段,React就会调用getDerivedStateFromError,如果异常发生在commit阶段,React会调用componentDidCatch。 这个异常可以是任何类型的异常, 捕捉到这个异常之后呢, 可以做一些补救之类的事情。
区别:componentDidCatch 是不会在服务器端渲染的时候被调用的 而 getDerivedStateFromError 会。
# 正题
- Suspense要解决的两个问题:
- 代码分片;
- 异步获取数据。
//假设我们有一个组件, 是看当前时间的, 它用了一个很大的第三方插件, 而我想只在用的时候再加载资源,不打在总包里。
import React from "react";
import moment from "moment";
const Clock = () => <h1>{moment().format("MMMM Do YYYY, h:mm:ss a")}</h1>;
export default Clock;
2
3
4
5
// Usage of Clock
const Clock = React.lazy(() => {
console.log("start importing Clock");
return import("./Clock");
});
2
3
4
5
这里我们使用了React.lazy, 这样就能实现代码的懒加载。 React.lazy 的参数是一个function, 返回的是一个promise. 这里返回的是一个import 函数, webpack build 的时候, 看到这个东西, 就知道这是个分界点。 import 里面的东西可以打包到另外一个包里。
使用
<Suspense fallback={<Loading />}>
{ showClock ? <Clock/> : null}
</Suspense>
2
3
showClock 为 true, 就尝试render clock, 这时候, 就触发另一个事件: 去加载clock.js 和它里面的 lib momment。
利用了 componentDidCatch 和 getDerivedStateFromError, 其实刚开始在v16的时候, 是要用componentDidCatch 的, 但它毕竟是commit phase 里的东西, 还是分出来吧, 所以又加了个getDerivedStateFromError来实现 Suspense 的功能。
# 异步获取数据
我们获取数据还是只能在commit phase 做, 也就是在componentDidMount 里 或者 didUpdate 里做。