前言
React开发过程中,大部分都是单页面应用,不做代码分片的话,所有的js文件都打成一个庞大的bundlejs文件,随着项目内容的不断增多,首屏空白时间就会变得越来越长。
因此可以对代码进行分片,打包时生成多个js文件,每次页面只请求所需要的js文件,用户体验大大提升。
1. 代码分片的3种角度
1.1 根据业务代码与依赖包进行切割
业务代码是会随着迭代的进行不断变化的,而依赖包则基本保持不变,因此可以将依赖包的代码单独打包成一个文件vendor.js,这个包基本保持不变,代码发布之后用户不用重新下载。
1.2 根据路由进行切割
用户进入首页时,其他页面的代码用户是不关心的,也是可以不用加载的,因此可以根据路由对代码进一步分割,此时代码将会分割为pageA.js,pageB.js。。。
1.3 根据组件进行切割
当某个组件在pageA与pageB中同时出现时,如果根据路由打包,则pageA与pageB中都包含该组件代码,因此就会存在代码冗余的问题。
如果从组件角度考虑组件化,就可以避免此问题,但是由此带来的问题就是包的数量会急剧增加,需要开发者自己衡量利弊。
2. 实现方案
2.1 Loadable实现
在React16.6之前懒加载流行使用Loadable包。
import Loadable from 'react-loadable';
const ComponentA = Loadable({
loading: () => <ALoading />, // 自定义loading组件,下载Component组件代码时展示
loader: () => import('./components/ComponentA'),
})
const ComponentB = Loadable({
loading: () => <BLoading />, // 自定义loading组件,下载Component组件代码时展示
loader: () => import('./components/ComponentB'),
})
const App = () => (
<Router>
<Navigator />
<Switch>
<Route path="a" component={ComponentA} />
<Route path="b" component={ComponentB} />
</Switch>
</Router>
)
2.2 React实现
现在React提供了Lazy,Suspense方法
import React, { Lazy, Suspense } from 'react';
const ComponentA = Lazy(() => import('./routes/ComponentA'));
const ComponentB = Lazy(() => import('./routes/ComponentB'));
const App = () => (
<Router>
<Navigator />
<Suspense fallback={<Loading />}>
<Switch>
<Route path="a" component={ComponentA} />
<Route path="b" component={ComponentB} />
</Switch>
</Suspense>
</Router>
)
2.3 添加ErrorBoundry错误边界
错误边界可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI
官网链接
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
3. 对redux进行分割
对页面进行分割后,可以进一步的切割页面所需要的数据,我们使用store的replaceReducer方法 在store.js中,添加一个维护当前reducer的对象,和一个更新reduer的方法。
let asyncReducers = {};
export const getNewReducer = (newModuleInfo) => {
asyncReducers[newModuleInfo.name] = newModuleInfo.reducer;
store.replaceReducer(combineReducer({
...asyncReducers,
}))
}
在reducerC文件中,需要调用该方法
import { getNewReducer } from './store.js';
const state = { value: '' };
const reducer = (state = state, action) => {
switch(action.type) {
case 'setValue':
return { ...state, value: action.value };
default:
return state;
}
}
export default getNewReducer({ name: 'reducerC', reducer });