React中Suspense及lazy()懒加载及代码分割原理和使用的理解
React中Suspense及lazy()懒加载原理和使用的理解
Suspense和lazy()都是react中比较新的特性,在项目中使用还比较少,但是学习一下有助于在后面的项目中使用,同样可以一窥React未来的发展方向
React.lazy()
概括
顾名思义lazy()方法是用来对项目代码进行分割,懒加载用的.只有当组件被加载,内部的资源才会导入
为什么需要懒加载
在React的项目中import导入其他组件和库都是默认在初始直接导入的,webpack等打包工具会将import导入的文件直接合并到一个大文件中,如果项目很大,打包完后初始化加载时需要加载的文件会很大,这时候就需要代码分割
官方文档中的例子
项目中:
// app.js
import { add } from './math.js';
console.log(add(16, 26)); // 42
// math.js
export function add(a, b) {
return a + b;
}
打包后
function add(a, b) {
return a + b;
}
console.log(add(16, 26)); // 42
如何进行代码分割
在你的应用中引入代码分割的最佳方式是通过动态 import()
语法,这是官方文档中说的
动态import例子:
静态导入:
import { add } from './math';
console.log(add(16, 26));
动态导入:
import("./math").then(math => {
console.log(math.add(16, 26));
});
使用了动态导入之后,webpack检测到这种语法会自动代码分割,也就是压缩到两个文件里
React.lazy()就是对这个种动态导入方式的优化方法
使用:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
// Displays <Spinner> until OtherComponent loads
<React.Suspense fallback={<Spinner />}>
<div>
<OtherComponent />
</div>
</React.Suspense>
);
}
发现没有原来的动态导入写法像 Promise
的写法链式调用,现在动态导入的方式,有点类似于Async
写出来是同步使用的,可以直接讲返回的Promise
对象作为组件使用,当这个组件pending
是显示的是Suspense
中fallback
的内容,只有resolve
才显示加载好的组件.
所以可以看出Suspense
和React.lazy()
需要结合在一起用,否则会报错缺少Placeholder UI`
Suspense
Suspense的英文意思是悬念,悬停**,Suspense的作用就是在遇到异步请求或者异步导入组件的时候等待请求和导入完成再进行渲染,**相比普通的组件, 我们是要求render是一个纯函数,一旦开始渲染不会等待,而有了Suspense后我们可以再render过程中写一部代码.
Suspense应用场景
Subspense
存在两种应用场景,第一种就是动态导入组件(如上),一种就是异步请求数据(暂时不支持,大概16.9版本)
动态导入组件
动态导入组件上面的例子就是上面的代码片段,就是当这个组件pending
是显示的是Suspense
中fallback
的内容,只有resolve
才显示加载好的组件,所以fallback
不能为空
优点
好处在于我们可以不用创建一些组件状态的变量来控制是否显示loading画面和真正组件,这部分逻辑直接由Suspense内部实现. 而且还有一种场景就是当一个父组件中,有多个动态加载的组件,Suspense可以直接将loading状态聚合,只有当所有组件加载完成才显示,这里免去了复杂的逻辑判断.
数据请求后渲染页面
Suspense
暂时还不支持数据请求后渲染, 预计是在16.9版本也就是2019年年中发布,但是以及有了实例使用的方法,下面这里就是官方的示例代码,这里做一些翻译解释
// unstable_createResource这个就是一个封装的请求数据的插件,不用太清楚类似于fetch
import {unstable_createResource} from 'react-cache';
// 声明请求数据的方法
const TodoResource = unstable_createResource(fetchTodo);
//内部请求TodoResource.read(props.id) 就是异步请求数据
function Todo(props) {
// Suspends until the data is in the cache
const todo = TodoResource.read(props.id);
return <li>{todo.title}</li>;
}
function App() {
return (
// 只有当两个Todo内部的异步请求都完成后才能渲染出来,否则渲染<Spinner/>
<React.Suspense fallback={<Spinner />}>
<ul>
{/* Siblings fetch in parallel */}
<Todo id="1" />
<Todo id="2" />
</ul>
</React.Suspense>
);
}
这就是Suspense请求数据时的使用的方法,同样解决了loading状态的问题,相当于再render过程中加入了异步副作用操作,再普通的组件中异步操作是不起作用的,因为先渲染完,异步数据才会返回,这时候已经渲染完了.
Suspense实现原理
Subspense的实现主要就是利用了**ComponentDidCatch
这个生命周期,这个什么周期就是用来捕获子组件树中的任何Javascript异常**
源码就不分析了,这里说一下大概的流程步骤:
- 父组件渲染到子组件时发现异步请求,直接抛出错误,捕获的结果是一个
Promise
对象 -
ComponentDidCatch
捕获这个Promise
对象,pending
状态下渲染fallback
的 - 当resolve时重新
render
,遇到下一个异步请求重复上面操作 - 直到整个父组件的抛出的
promise
对象都为resolve,将loading
替换为真正的组件.
总结
Suspense其实就是将原来放在外面处理的异步请求也就是副作用放到渲染过程中进行操作,这样render这个函数就不再是纯函数了,但是非常直观方便,不需要再用很多状态来控制loading显示,而异步请求的结果无法预测会导致很多bug.等到Suspense
支持数据请求的场景时,我会考虑把它运用到自己的项目中, 感觉确实方便很多本来就觉得Loading状态有点多余,虽然现在用的是Dva,会自动给effect创建loading状态的,还不是特别需要.