Suspense,lazy 是 React 16.x 的新特性 话不多说让我们开始吧
- 在你的应用中引入代码分割的最佳方式是通过动态 import() 语法。
注意: 动态 import() 语法目前只是一个 ECMAScript (JavaScript) 提案, 而不是正式的语法标准。预计在不远的将来就会被正式接受。
Webpack 解析到该语法时,它会自动地开始进行代码分割。如果你使用 Create React App,该功能已配置好,你能立刻使用 这个特性。Next.js 也已支持该特性而无需再配置。 如果你自己配置 Webpack,你可能要阅读下 Webpack 关于代码分割的指南。你的 Webpack 配置应该类似于此。 当使用 Babel 时,你要确保 Babel 能够解析动态 import 语法而不是将其进行转换。对于这一要求你需要 babel-plugin-syntax-dynamic-import 插件。
React.lazy
注意: React.lazy 和 Suspense 技术还不支持服务端渲染。如果你想要在使用服务端渲染的应用中使用,我们推荐 Loadable Components 这个库。它有一个很棒的服务端渲染打包指南。
React.lazy 函数能让你像渲染常规组件一样处理动态引入(的组件)。
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
<OtherComponent />
</div>
);
}
这个函数他返回了一个Promise,需要用函数去调用,而且还是主动抛错的,resolve
- 这样做就完成了组件的异步加载,但是这样做有个问题那就是: 如果在 Home 渲染完成后,包含 OtherComponent 的模块还没有被加载完成,我们可以使用加载指示器为此组件做优雅降级。这里我们使用 Suspense 组件来解决。
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const TowComponent = React.lazy(() => import('./TowComponent'))
function MyComponent() {
return (
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
<TowComponent />
</Suspense>
);
}
- 如果在 MyComponent 渲染完成后,包含 OtherComponent 的模块还没有被加载完成,我们可以使用加载指示器为此组件做优雅降级。这里我们使用 Suspense 组件来解决。
- Suspense 作用域取件可以接受多个组件
MyErrorBoundary
那么 我们继续问题,懒加载的组件,在加载的过程中遇到一个问题如(网络问题)我们该怎么样来维护用户体验呢?——不至于让用户看到空白 它会触发一个错误。你可以通过异常捕获边界**(Error boundaries)**
注意 错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于 JavaScript 中 catch {} 的工作机制。
还是上面代码:
import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const TowComponent = React.lazy(() => import('./TowComponent'))
function MyComponent() {
return (
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
<TowComponent />
</Suspense>
<MyErrorBoundary>
);
}
MyErrorBoundary 如下【选自官方文档】
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, info) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default MyErrorBoundary ;
基于路由代码分割
好让我们回来 在SPA 单页面程序中,我们可能要在多个页面中用到组件的懒加载,这样的话我们就可以吧他定义在路由中 很简单 套进去就可以了
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
命名导出(Named Exports)
接下来我们就会想了,既然是import
那是不是也可以向导入组件那种 进行 重命名
错误示范
const Tow = React.lazy(() => import('./TowComponent'))
注意:不可以直接这样子做 ,eact.lazy 目前只支持默认导出(default exports)。如果你想被引入的模块使用命名导出(named exports),你可以创建一个中间模块,来重新导出为默认模块。这能保证 tree shaking 不会出错,并且不必引入不需要的组件。
// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));
但是 真的有必要这样子做吗?