basic points
- 基于路由的代码分割和基于组件的代码分割
webpack可以设置多入口文件,可以通过入口文件进行代码分割,即所谓的基于路由的分割;基于路由的分割可以常见于多tab页切换等场景;
但是由于即使是多入口文件,某个文件中的代码也未必是需要首屏全部加载的,比如弹窗等;如果可以基于组件进行代码分割,可以更加细粒度地控制首屏需要加载的代码及其他代码的加载时机,可以有效提高页面性能和用户体验;
- 考虑自己实现一个动态载入的组件,需要考虑哪些问题
-
需要基于
ES6
新特性import()
实现,然后通过states
和生命周期函数共同控制组件的可见性及展示内容,直接看官方demo:class MyComponent extends React.Component { state = { Bar: null }; componentWillMount() { import('./components/Bar').then(Bar => { this.setState({ Bar: Bar.default }); }); } render() { let {Bar} = this.state; if (!Bar) { return <div>Loading...</div>; } else { return <Bar/>; }; } }
import()
,比如SSR
怎么处理,比如加载完成前的站位组件不可能只是单纯的静态文本Loading...
等; -
React Loadable
是一个轻量级的封装库,可以实现基于组件的代码分割功能。
看下React Loadable
的使用、实现及对应问题的解决方式:
-
使用:
import React from "react"; import "./styles.css"; import Loadable from "react-loadable"; const LoadableSub = Loadable({ loader: () => import("./Sub"), loading: () => { return <div>Loading...</div>; } }); export default function App() { return ( <div className="App"> <h1>Hello CodeSandbox</h1> <h2>Start editing to see some magic happen!</h2> <LoadableSub /> </div> ); } ```
-
Loadable
实质是一个高阶组件,形式为Loadable({ loader: () => import('xxx'), // component to be loaded dynamically. loading: <HoldingComponent /> // 预占位组件,动态组件未加载时显示loading信息等 })
-
构建高可用的
Loading
组件——错误处理、避免占位信息和有效信息展示交替时的闪烁、加载超时、预加载、同时加载多个资源等;-
loader
加载失败处理:失败时loading
会接收到一个error
的prop
对象,否则为null
;function Loading(props) { if (props.error) { return <div>Error! <button onClick={ props.retry }>Retry</button></div>; } else { return <dxiv>Loading...</div>; } }
-
Loadable
支持设置设置一个默认的延迟时间,超过该时间才会显示loading
;对应的Loadable
属性为delay
默认值为200ms
,超过则loading
的名为pastDelay
的prop
为true
;Loadable({ loader: () => import('xxx') loading: Loading, delay: 300, // ms }); const Loading = (props) => { if (props.error) { return <div>Woops! Error occured.<button onClick={props.retry}>Retry</button><div> } else if (props.pastDelay) { return <div>Loading...</div> } else { return null; } };
-
loader
因网络问题等加载失败处理——Loadable
支持设置timeout
,默认被禁用;对应在loading
组件中可以通过props.timeout
属性获取状态,超时为true
;const LoadableSub = Loadable({ loader: () => import("./Sub"), loading: Loading, delay: 500, timeout: 8000 }); const Loading = props => { if (props.error) { return <div>Woops, errors occurred!</div>; } else if (props.pastDelay) { return <div>Loading...</div>; } else if (props.timeOut) { return <div>Timeout, please retry</div>; } else { return null; } };
-
动态加载多个资源,可以使用
Loadable.map({})
; -
Loadable
支持预加载,单个组件的预加载使用方法类似于LoadableSub.preload()
; -
客户端进行动态加载,可能会出现白屏等,所以如果
SSR
时能够对动态加载做一定的处理,那么可以优化该体验;This really sucks, but the good news is that React Loadable is designed to make server-side rendering work as if nothing is being loaded dynamically.
-
-
服务端渲染
- 确保渲染前对应的组件已加载就绪。预加载所有
loadable component
——使用Loadable.preloadAll
,它返回一个promise
对象,并且在所有loadable component
就绪后resolve
;Loadable.preloadAll().then(() => { app.listen(3000, () => { console.log('Running on http://localhost:3000/'); }); });
- 客户端如何处理
SSR APP
a. 声明需要加载的模块,使用Loadable
的opt.modules
和webpack
;另外,需要在babel
配置中加入以下配置信息
b. 确定当某个请求完成后,哪些组件会被渲染;Loadable({ loader: () => import('./Bar'), modules: ['./Bar'], webpack: () => [require.resolveWeak('./Bar')], }); // babel配置 { "plugins": [ "react-loadable/babel" ] }
Loadable.Capture
应运而生,可以记录已渲染的模块;import express from 'express'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; import App from './App'; import Loadable from 'react-loadable'; const app = express(); app.get('/', (req, res) => { let modules = []; let html = ReactDOMServer.renderToString( <Loadable.Capture report={moduleName => modules.push(moduleName)> <App/> </Loadable.Capture> ); console.log({ modules }); res.end(`${html}`); });
- 确保渲染前对应的组件已加载就绪。预加载所有