react-router4 实现按需加载

7,974 阅读2分钟

按需加载的背景

React Router 是一个非常出色的路由解决方案,同时也非常容易上手。但是当网站规模越来越大的时候,首先出现的问题是 Javascript 文件变得巨大,这导致首页渲染的时间让人难以忍受。实际上程序应当只加载当前渲染页所需的 JavaScript,也就是大家说的“代码分拆" — 将所有的代码分拆成多个小包,在用户浏览过程中按需加载。

老的方案

这里所说的老是指使用react-router低于4的版本。

在低版本(小于4)的react-router中直接提供了按需加载的方案,示例如下:

const rootRoute = {
  path: '/',
  indexRoute: {
    getComponent(nextState, cb) {
      require.ensure([], (require) => {
        cb(null, require('components/layer/HomePage'))
      }, 'HomePage')
    },
  },
  getComponent(nextState, cb) {
    require.ensure([], (require) => {
      cb(null, require('components/Main'))
    }, 'Main')
  },
  childRoutes: [
    require('./routes/baidu'),
    require('./routes/404'),
    require('./routes/redirect')
  ]
}

ReactDOM.render(
  (
    <Router
      history={browserHistory}
      routes={rootRoute}
      />
  ), document.getElementById('app')
);

核心代码是router.getComponent,然而在react-router4中,没有router.getComponent了,这样我们该如何实现按需加载呢?

react-router4的实现方案

根据官网的介绍:

One great feature of the web is that we don’t have to make our visitors download the entire app before they can use it.
You can think of code splitting as incrementally downloading the app. While there are other tools for the job, we’ll use Webpack and the bundle loader in this guide.

我们要借助bundle-loader来实现按需加载。

首先,新建一个bundle.js文件:

import React, { Component } from 'react'

export default class Bundle extends React.Component {

    state = {
        // short for "module" but that's a keyword in js, so "mod"
        mod: null
    }

    componentWillMount() {
        this.load(this.props)
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.load !== this.props.load) {
            this.load(nextProps)
        }
    }

    load(props) {
        this.setState({
            mod: null
        })
        props.load((mod) => {
            this.setState({
                // handle both es imports and cjs
                mod: mod.default ? mod.default : mod
            })
        })
    }

    render() {
        if (!this.state.mod)
            return false
        return this.props.children(this.state.mod)
    }
}

然后,在入口处使用按需加载:


// ...

// bundle模型用来异步加载组件
import Bundle from './bundle.js';

// 引入单个页面(包括嵌套的子页面)
// 同步引入
import Index from './app/index.js';
// 异步引入
import ListContainer from 'bundle-loader?lazy&name=app-[name]!./app/list.js';

const List = () => (
    <Bundle load={ListContainer}>
        {(List) => <List />}
    </Bundle>
)

// ...

    <HashRouter>
        <Router basename="/">
            <div>
                <Route exact path="/" component={Index} />
                <Route path="/list" component={List} />
            </div>
        </Router>
    </HashRouter>

// ...

webpack.config.js文件配置

output: {
    path: path.resolve(__dirname, './output'),
    filename: '[name].[chunkhash:8].bundle.js',
    chunkFilename: '[name]-[id].[chunkhash:8].bundle.js',
},

完整代码示例

bundle.js

react-molin