输入关键词开始搜索

react实现按需加载-代码分割(上)

490 字 约 2 分钟

前言 ​

我们直接使用webpack配置的react环境,在编译打包之后,页面只生成了一个文件bundle.js,这样在首屏加载时就会很慢,因为加载一个页面的时候也同时加载了其他页面的js,首次进入页面时,只加载自己的JS,这样首屏加载就会快很多。

一、按需加载 ​

1.首屏加载需要使用bundle-loader这个插件,所以先安装这个插件

shell
npm install bundle-loader --save-dev

2.新建Bundle.js

js
import React, {Component} from 'react'

class Bundle extends Component {
    constructor(props){
        super(props);
        this.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() {
        return this.props.children(this.state.mod)
    }
}

export default Bundle;

此文件是官方提供,参考文档 Code Splitting

3.改造路由器

js
// 原路由文件
import React from 'react';
import {Route,Switch,Redirect} from 'react-router-dom';
import Login from './container/login/login'
import Register from "./container/register/register";
import Bossinfo from "./container/bossinfo/bossinfo";
import Geniusinfo from "./container/geniusinfo/geniusinfo";
import AuthRoute from "./components/authroute/authroute";
import Dashboard from "./components/dashboard/dashboard";
import Chat from './components/chat/chat';


class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            hasError:false
        }
    }

    componentDidCatch(err,info){
        console.log(err,info);
        this.setState({
            hasError:true
        })
    }

    render(){
        return(
            this.state.hasError ? <Redirect to='/msg'></Redirect>
                :
            <div>
                <AuthRoute/>
                <Switch>
                    <Route path='/login' component={Login}></Route>
                    <Route path='/register' component={Register}></Route>
                    <Route path='/bossinfo' component={Bossinfo}></Route>
                    <Route path='/geniusinfo' component={Geniusinfo}></Route>
                    <Route path='/chat/:user' component={Chat}></Route>
                    <Route component={Dashboard}></Route>
                </Switch>
            </div>
        )
    }
}

export default App;

改造:

js
import React from 'react';
import {Route,Switch,Redirect} from 'react-router-dom';
import Bundle from './Bundle';
import Login from 'bundle-loader?lazy&name=login!./container/login/login'
import Register from "bundle-loader?lazy&name=register!./container/register/register";
import Bossinfo from "bundle-loader?lazy&name=bossinfo!./container/bossinfo/bossinfo";
import Geniusinfo from "bundle-loader?lazy&name=geniusinfo!./container/geniusinfo/geniusinfo";
import AuthRoute from "bundle-loader?lazy&name=authroute!./components/authroute/authroute";
import Dashboard from "bundle-loader?lazy&name=dashboard!./components/dashboard/dashboard";
import Chat from 'bundle-loader?lazy&name=chat!./components/chat/chat';


const Loading = function () {
    return <div>Loading...</div>
};

// 调用bundle高阶组件,包裹原始组件
const createComponent = (component) => (props) => (
    <Bundle load={component}>
        {
            (Component) => Component ? <Component {...props}/> : <Loading/>
        }
    </Bundle>
);



class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            hasError:false
        }
    }

    componentDidCatch(err,info){
        console.log(err,info);
        this.setState({
            hasError:true
        })
    }

    render(){
        return(
            this.state.hasError ? <Redirect to='/msg'></Redirect>
                :
            <div>
                <AuthRoute/>
                <Switch>
                    <Route path='/login' component={createComponent(Login)}></Route>
                    <Route path='/register' component={createComponent(Register)}></Route>
                    <Route path='/bossinfo' component={createComponent(Bossinfo)}></Route>
                    <Route path='/geniusinfo' component={createComponent(Geniusinfo)}></Route>
                    <Route path='/chat/:user' component={createComponent(Chat)}></Route>
                    <Route component={createComponent(Dashboard)}></Route>
                </Switch>
            </div>
        )
    }
}

export default App;

现在你可以npm start,打开浏览器,看是不是进入新的页面,都会加载自己的JS的~

但是发现,名字都是0.bundle.js这样子的,分不清楚是哪个页面的js。

修改下webpack.dev.config.js,加个chunkFilename。chunkFilename是除了entry定义的入口js之外的js~

json
    output: {
        path: path.join(__dirname, './dist'),
        filename: 'bundle.js',
        chunkFilename: '[name].js'
    }

现在运行发现名字变成home.js,这样的了。

那么问题来了home是在哪里设置的?webpack怎么知道他叫home?

其实在这里我们定义了,router.js里面

import Home from 'bundle-loader?lazy&name=home!pages/Home/Home';

这里有个name=home。

二、缓存 ​

想象一下这个场景~

我们网站上线了,用户第一次访问首页,下载了home.js,第二次访问又下载了home.js~

这肯定不行呀,所以我们一般都会做一个缓存,用户下载一次home.js后,第二次就不下载了。

有一天,我们更新了home.js,但是用户不知道呀,用户还是使用本地旧的home.js。出问题了~

怎么解决?每次代码更新后,打包生成的名字不一样。比如第一次叫home.a.js,第二次叫home.b.js。

文档看这里

我们照着文档来

webpack.dev.config.js

json
    output: {
        path: path.join(__dirname, './dist'),
        filename: '[name].[hash].js',
        chunkFilename: '[name].[chunkhash].js'
    }

每次打包都用增加hash~

现在我们试试,是不是修改了文件,打包后相应的文件名字就变啦?

但是你可能发现了,网页打开报错了~因为你dist/index.html里面引用js名字还是bundle.js老名字啊,改成新的名字就可以啦。

这样每次都去改名字是不现实的。