React Router v4 之代码分割:从放弃到入门
背景介绍React Router v4 推出已有六个月了,网络上因版本升级带来的哀嚎仿佛就在半年前。我在使用这个版本的 React Router 时,也遇到了一些问题,比如这里所说的代码分割,所以写了这篇博客作为总结,希望能对他人有所帮助。 什么是代码分割(code splitting)在用户浏览我们的网站时,一种方案是一次性地将所有的 JavaScript 代码都下载下来,可想而知,代码体积会很可观,同时这些代码中的一部分可能是用户此时并不需要的。另一种方案是按需加载,将 JavaScript 代码分成多个块(chunk),用户只需下载当前浏览所需的代码即可,用户进入到其它页面或需要渲染其它部分时,才加载更多的代码。这后一种方案中用到的就是所谓的代码分割(code splitting)了。 当然为了实现代码分割,仍然需要和 webpack 搭配使用,先来看看 webpack 的文档中是如何介绍的。 Webpack 文档的 code splitting 页面中介绍了三种方法:
你可以读一下此篇文档,从而对 webpack 是如何进行代码分割的有个基本的认识。本文后面将提到的方案就是基于上述的第三种方法。 React Router 中如何进行代码分割在 v4 之前的版本中,一般是利用 使用 bundle-loader 的方案在 React Router v4 官方给出的文档中,使用了名为 其主要实现思路为创建一个名为 示例代码如下: import loadSomething from 'bundle-loader?lazy!./Something' <Bundle load={loadSomething}> {(mod) => ( // do something w/ the module )} </Bundle> 更多关于 使用 bundle-loader 方法存在的不足之处这里提到的两个缺点我们在实际开发工作中遇到的,与我们的项目特定结构相关,所以你可能并不会遇上。 一、 代码丑陋 由于我们的项目是从 React Router v2,v3 升级过来的,在之前的版本中对于异步加载的实现采用了集中配置的方案,即项目中存在一个 但是在 React Router v4 版本中,由于使用了 import loadSomething from 'bundle-loader?lazy!./Something' 而我们的
当用这种方法引入的模块数量过多时,文件将会不忍直视。 二、 存在莫名的组件生命周期Bug 在使用了这种方案后,在某些页面中会出现这样的一个Bug:应用中进行页面跳转时,上一个页面的组件会在 当然,这个Bug只与我自己的特定项目有关,错误原因可能与 使用 import() 的新方案Dan Abramov 在这个 create-react-app 的 issue 中给出了 代码分割和 React Router v4一个常规的 React Router 项目结构如下: // 代码出处: // http://serverless-stack.com/chapters/code-splitting-in-create-react-app.html /* Import the components */ import Home from './containers/Home'; import Posts from './containers/Posts'; import NotFound from './containers/NotFound'; /* Use components to define routes */ export default () => ( <Switch> <Route path="/" exact component={Home} /> <Route path="/posts/:id" exact component={Posts} /> <Route component={NotFound} /> </Switch> ); 首先根据我们的 route 引入相应的组件,然后将其用于定义相应的 但是,不管匹配到了哪一个 route,我们这里都一次性地引入所有的组件。而我们想要的效果是当匹配了一个 route,则只引入与其对应的组件,这就需要实现代码分割了。 创建一个异步组件(Async Component)异步组件,即只有在需要的时候才会引入。 import React,{ Component } from 'react'; export default function asyncComponent(importComponent) { class AsyncComponent extends Component { constructor(props) { super(props); this.state = { component: null,}; } async componentDidMount() { const { default: component } = await importComponent(); this.setState({ component: component }); } render() { const C = this.state.component; return C ? <C {...this.props} /> : null; } } return AsyncComponent; }
在 使用异步组件(Async Component)不再使用如下静态引入组件的方法: import Home from './containers/Home'; 而是使用 const AsyncHome = asyncComponent(() => import('./containers/Home')); 此处的 注意这里并没有进行组件的引入,而是传给了 最后如下使用这个 <Route path="/" exact component={AsyncHome} /> 对于 reducer 和 saga 文件的异步加载在上面的这篇文章中,只给出了对于组件的异步引入的解决方案,而在我们的项目中还存在将 processReducer(reducer) { if (Array.isArray(reducer)) { return Promise.all(reducer.map(r => this.processReducer(r))); } else if (typeof reducer === 'object') { const key = Object.keys(reducer)[0]; return reducer[key]().then(x => { injectAsyncReducer(key,x.default); }); } } 将需要异步引入的 reducer 作为参数传入,利用 Promise 来对其进行异步处理。在 // componentDidMount 中做如下修改 async componentDidMount() { const { default: component } = await importComponent(); Promise.all([this.processReducer(reducers),this.processSaga(sagas)]).then(() => { this.setState({ component }); }); } 在上面对 injectAsyncReducer(key,x.default); 其作用是利用 Redux 中的 // reducerList 是你当前的 reducer 列表 function createReducer(asyncReducers) { asyncReducers && !reducersList[Object.keys(asyncReducers)[0]] && (reducersList = Object.assign({},reducersList,asyncReducers)); return combineReducers(reducersList); } function injectAsyncReducer(name,asyncReducer) { store.replaceReducer(createReducer({ [name]: asyncReducer })); } 完整的 asyncComponent 代码可见此处 gist,注意一点,为了能够灵活地使用不同的 asyncComponent 方法与 React Router v4 的结合使用组件、reducer、saga 的异步引入考虑到代码可读性,可在你的 // 引入前面所写的异步加载函数 import asyncComponent from 'route/AsyncComponent'; // 只传入第一个参数,只需要组件 export const AsyncHomePage = asyncComponent(() => import('./homepage/Homepage')); // 传入三个参数,分别为 component,reducer,saga // 注意这里的第二个参数 reducer 是一个对象,其键值对应于redux store中存放的键值 export const AsyncArticle = asyncComponent( () => import('./market/Common/js/Container'),{ market: () => import('./market/Common/js/reducer') },() => import('./market/Saga/watcher') ); // reducer 和 saga 参数可以传入数组 // 当只有 saga,而无 reducer 参数时,第二项参数传入空数组 [] const UserContainer = () => import('./user/Common/js/Container'); const userReducer = { userInfo: () => import('./user/Common/js/userInfoReducer') }; const userSaga = () => import('./user/Saga/watcher'); export const AsyncUserContainer = asyncComponent( UserContainer,[userReducer,createReducer],[userSaga,createSaga] ); 然后在项目的 // route/index.jsx <Route path="/user" component={AsyncArticle} /> <Route path="/user/:userId" component={AsyncUserContainer} /> 根据 React Router v4 的哲学,React Router 中的一切皆为组件,所以不必在一个单独的文件中统一配置所有的路由信息。建议在你最外层的容器组件,比如我的 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- 使用SQLALCHEMY连接到Oracle数据库
- Ajax 使用net独有控件ScriptManager实现无刷新效果【登陆】
- iphone – Xcode Organizer:找不到有效的签名身份
- ruby – Jekyll无法部署 – 部署时无法安装Sass-Listen-4.0
- Ajax Zero to Hero (2)入门代码的简单封装
- react-native – 安装软件包后运行’无法读取属性’ReactCu
- C#设计模式之十一享元模式(Flyweight Pattern)【结构型】
- 深入理解Ruby中的block概念
- c# – 获取GridView列标题文本 – 始终返回空白
- Cocos2D中相关问题提问的几个论坛