react代码分片

2,680 阅读3分钟

前言

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 });