react-router4代码分割

react-router4代码分割

react-router4官方文档: https://reacttraining.com/react-router/web/guides/code-splitting

对于SPA项目,出于加载速度的考虑,肯定不能在页面初始化的时候加载全部js文件,因此需要将代码分块,也就是所谓的code splitting,我这里基于react-router4.1.1展示一个code split的例子,当然,你也可以参考react官方文档

思路解析

react-router4是对之前react-router的一次大改,按照官方的说法,是将路由的问题转变成了react组件的问题,所以在react-router4里面,不同于以往使用require.ensure,我们使用一些其他办法异步的请求组件的js文件。

方案A 使用bundle-loader

bundle-loader git地址:https://github.com/webpack-contrib/bundle-loader

先仿着官方的示例先写一个Bundle组件,简化了一下,大概会是这样的


{/*
// 调用示例
<Bundle load={require('bundle-loader?lazy!./somefile.js')}>
    {(Cmp) => <Cmp></Cmp>}
</Bundle>
*/}

// Bundle.js
import React, { Component } from 'react'

class Bundle extends Component {
  constructor() {
    super()
    this.state = {
      mod: null
    }
  }

  componentDidMount() {
    this.props.load((mod) => {
      this.setState({
        mod: mod.default || mod
      })
    })
  }

  render() {
    return (
      this.state.mod ? this.props.children(this.state.mod) : null
    )
  }
}

export default Bundle

在被传入的load方法被调用的时候,相应的js文件才会被请求和加载

然后在入口的路由文件里面这样写(假设我们有两个组件,Cp1, Cp2)

import React from 'react'
import {
  BrowserRouter,
  Route
} from 'react-router-dom'
import Bundle from './bundle'

let CodeSplit = () => {
  return (
    <BrowserRouter>
      <div>
        <Route path={'/cp1'} render={() => {
          return (<Bundle load={require('bundle-loader?lazy!./cp1')}>
            {(Cp1) => <Cp1></Cp1>}
          </Bundle>)
        }
        }></Route>

        <Route path={'/cp2'} render={() => {
          return (<Bundle load={require('bundle-loader?lazy!./cp2')}>
            {(Cp2) => <Cp2></Cp2>}
          </Bundle>)
        }
        }></Route>
      </div>
    </BrowserRouter>
  )
}

export default CodeSplit

这样,代码分割就完成了。

注意

这里有一个小坑,如果你跟我一样使用的是create-react-app的话,你会发现,在运行代码的时候,会报这个错误

Line 35:  Unexpected '!' in 'bundle-loader?lazy!./cp1'. Do not use import syntax to configure webpack loaders  import/no-webpack-loader-syntax

Line 42:  Unexpected '!' in 'bundle-loader?lazy!./cp2'. Do not use import syntax to configure webpack loaders  import/no-webpack-loader-syntax

这是因为create-react-app不支持webpack-loader,具体的可以看看这个issue

why I can't use bundle-loader like this: https://github.com/facebookincubator/create-react-app/issues/2477

解决办法也很简单,采用方案B

方案B 使用import()

import() 属于es的一个proposal,也就是提案,还没有正式立项,所以具体会有什么问题我这里也不清楚,不过babel已经支持,所以我们这里可以尝试使用,将之前使用bundle-loader的例子改造一下

因为import返回一个promise,所以我们这里将componentDidMount变成一个async函数

{/*
// 调用方法
<Bundle load={() => import(./somefile.js)}></Bundle>
*/}

import React, { Component } from 'react'

class Bundle extends Component {
  constructor() {
    super()
    this.state = {
      mod: null
    }
  }

  async componentDidMount() {
    const {default: mod} = await this.props.load()

    this.setState({
      mod: mod.default || mod
    })
  }

  render() {
    return (
      this.state.mod ? this.props.children(this.state.mod) : null
    )
  }
}

export default Bundle

然后在入口文件的路由里面这么用

import React from 'react'
import {
  BrowserRouter,
  Route
} from 'react-router-dom'
import Bundle from './bundle'

let CodeSplit = () => {
  return (
    <BrowserRouter>
      <div>
        <Route path={'/cp1'} render={() => {
          return (<Bundle load={() => import('./cp1')}>
            {(Cp1) => <Cp1></Cp1>}
          </Bundle>)
        }
        }></Route>

        <Route path={'/cp2'} render={() => {
          return (<Bundle load={() => import('./cp2')}>
            {(Cp2) => <Cp2></Cp2>}
          </Bundle>)
        }
        }></Route>
      </div>
    </BrowserRouter>
  )
}


export default CodeSplit

OK,一个大致的代码分割功能差不多就完成了

*************** 2017.12.7 更新 ****************

方案B改进版

方案B虽然实现了我们异步加载组件的需求,但是调用还是显得比较麻烦,我们需要一种更优雅的方式来实现异步加载,同时还希望能传递参数给组件和自定义组件在加载时候的显示效果,所以这里对方案B进一步进行封装

因为代码比较简单,所以我这里直接把我项目里的代码贴过来了

// async-component.js
/**
 * 用于react router4 code splitting
 */
import React, {Component} from 'react'

/**
 * @param {Function} loadComponent e.g: () => import('./component')
 * @param {ReactNode} placeholder  未加载前的占位
 */
export default (loadComponent, placeholder = null) => {
  class AsyncComponent extends Component {
    unmount = false

    constructor() {
      super()

      this.state = {
        component: null
      }
    }

    componentWillUnmount() {
      this.unmount = true
    }

    async componentDidMount() {
      const {default: component} = await loadComponent()

      if(this.unmount) return

      this.setState({
        component: component
      })
    }

    render() {
      const C = this.state.component

      return (
        C ? <C {...this.props}></C> : placeholder
      )
    }
  }

  return AsyncComponent
}

整体思路和之前的代码是一致的
然后调用的时候只需这么写

Demo组件,就是一个简单的无状态组件

// demo.jsx
import React from 'react'

const Demo = () => {
  return (
    <div>demo</div>
  )
}

export default Demo

调用示例

import asyncComponent from './async-component'

// 获取到异步组件
const AsyncDemo = asyncComponent(() => import('./demo'))

//...
render() {
  return (
    <Route path="/demo" component={AsyncDemo}></Route>
  )
}

// 如果要传参
render() {
  return (
    <Route path="/demo" render={() => {
        <AsyncComponent test="hello"></AsyncComponent>
    }}></Route>
  )
}

参数也可以通过asyncComponent函数进行传递,不过需要更改下async-component.js的代码,因为比较简单,所以这里就不展示了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容