import()异步加载模块
在webpack中, 调用import()函数可以将依赖模块进行切割, 打包为非入口点文件, 这是通过Promise+ajax完成的. 请求路径是相对路径, 对于单页应用来说没有问题.
非入口点文件的命名由webpack.config.output.chunkFilename(可以定义路径, 使用[name]和[id]变量)以及Magic Comment(定义[name]变量)共同确定.
React.lazy()函数
React.lazy() 允许你定义一个动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。
// 这个组件是动态加载的
const SomeComponent = React.lazy(() => import('./SomeComponent'));
渲染 lazy 组件依赖该组件渲染树上层的 <React.Suspense> 组件。这是指定加载指示器(loading indicator)的方式。
使用 React.lazy 的动态引入特性需要 JS 环境支持 Promise。在 IE11 及以下版本的浏览器中需要通过引入 polyfill 来使用该特性。
! 不支持服务端渲染。
例子 -- lazy加载组件
import { Component, lazy, Suspense } from 'react';
import css from './style.css';
/** 异步组件使用lazy()函数加载, 传递一个使用import()函数的Promise异步方法, 该方法最终返回import()函数的结果 */
const AsyncComponent = lazy(() => {
return new Promise((resolve, reject) => {
import('./AsyncComponent').then(AsyncComponent => {
console.log('加载完毕, 延迟传送');
setTimeout(() => {
console.log('传送');
resolve(AsyncComponent);
}, 5000);
});
});
});
export default (
<div id={css.app}>
<Suspense fallback={<h1>加载中</h1>}>
<AsyncComponent></AsyncComponent>
</Suspense>
</div>
);
Suspense组件
React关注DOM和事件, 数据更新更是重要, 因此, 我们来看Suspense组件如何在异步操作与UI更新之间建起桥梁.
代码分割: https://zh-hans.reactjs.org/docs/code-splitting.html#reactlazy
Suspense: https://zh-hans.reactjs.org/docs/concurrent-mode-suspense.html
Suspense还用于异步数据的获取:
官方示例: https://codesandbox.io/s/frosty-hermann-bztrp?file=/src/fakeApi.js
抛出Promise: 使用throw关键字陷入React内核
在执行异步操作的过程中, 我们只需要在Promise未完成状态时将该Promise抛出到React核心即可:
function fetchName() {
console.log('尝试联网获取用户名...');
throw new Promise(()=> {
console.log('抛出一个永久pending状态的Promise');
});
}
function FunctionComponent(props) {
console.log('尝试获取用户名并渲染UI...');
return <h2>用户名: {fetchName()}</h2>
}
export default (
<div id={css.app}>
<Suspense fallback={<h1>正在联网获取用户名...</h1>}>
<FunctionComponent/>
</Suspense>
</div>
);
Promise的拒绝状态会导致组件立即重新渲染, 并可能不断重复:
function fetchName() {
console.log('尝试联网获取用户名...');
throw new Promise((_, reject)=> {
console.log('抛出一个拒绝状态的Promise');
reject();
});
}
抛出其它非Promise异常会被React重新抛出, 导致页面报错.
如果一个抛出的Promise结束了成为success状态, 那么它之后应该返回相应的结果, 而不是再次抛出Promise, 因为success状态的Promise(这里可能是全部的Promise结束后再调用)会导致方法组件再次被调用以渲染元素.
import { Component, lazy, Suspense, createElement, useRef } from 'react';
import css from './style.css';
// 单例
let fetchNameByInternet = () => new Promise(resolve => {
console.log('网络请求开始了, 将于4秒后完成');
setTimeout(() => resolve('develon'), 4000);
});
let resolved = false; // 标志网络请求是否已完成
let name = "Don't get the Name"; // 存储从网络获取的用户名
function fetchName() {
console.log('尝试联网获取用户名...');
if (resolved) {
console.log(`获取到数据: "${name}" !`);
return name;
}
throw fetchNameByInternet().then(network_name => {
resolved = true;
name = network_name;
});
}
function FunctionComponent(props) {
console.log('方法组件被调用, 尝试获取用户名并渲染UI...');
let name = fetchName(); // 此处会抛出异常, 不可进行捕获, 从而就像CPU中断指令一样陷入React内核
console.log('方法组件继续执行, 开始渲染元素'); // 整个方法结束, 只有当Promise成功之后, 才会再次调用该方法组件, 所以说这些异步操作是有顺序的
return <h2>用户名: {name}</h2>;
}
export default (
<div id={css.app}>
<Suspense fallback={<h1>正在联网获取用户名...</h1>}>
<FunctionComponent/>
</Suspense>
</div>
);