当前位置: 移动技术网 > IT编程>网页制作>CSS > React+Redux项目中的代码分割

React+Redux项目中的代码分割

2018年12月26日  | 移动技术网IT编程  | 我要评论

react+redux 项目中的代码分割

准备一个基本项目

直接使用 create-react-app 初始化一个 react 项目,安装 react-router 和 redux。

接着创建两个路由页面,一个主页和一个登录页面,主页中有一个按钮可以跳转到登录页面。

// index.js
import react from 'react';
import reactdom from 'react-dom';

import routerconfig from './router';

reactdom.render(, document.getelementbyid('root'));

// router.js
import react from 'react';
import { browserrouter as router, switch, route } from 'react-router-dom';

import home from './route/home';
import login from './route/login';

function routerconfig() {
  return (
      
        
          
          
        
      
  );
}

export default routerconfig;

// route/home.js
import react from 'react';
import { link } from 'react-router-dom';

export default () => {
  return (

主页

登录页面 ); } // route/login.js import react from 'react'; export default () => { return (登录页面 ); }

此时执行 yarn build 操作,主页和登录页面的代码会打包到一个文件中。

 

main_js.png

分割路由页面

通过动态导入路由分割路由页面从而实现按需加载,webpack 内置了该功能。此外,还需要使用 @babel/syntax-dynamic-import 插件提供语法支持,对应的 babel 配置如下:

{
  "presets": [
    "react-app"
  ],
  "plugins": [
    "@babel/syntax-dynamic-import"
  ]
}

调整 router.js 代码,动态导入页面组件。

const home = () => import('./route/home');
const login = () => import('./route/login');

此时执行 yarn build 可以发现已经将代码进行了分割。但是页面无法正常访问了,因为动态导入是一个异步操作,它返回的并不是一个组件而是 promise。

我们可以创建一个叫做 asynccomponent 的组件,它接受动态导入的返回值作为属性,在内部会判断组件是否加载完毕,如果加载完毕则渲染组件,否则显示 loading。

// component/asynccomponent.js

import react, { component } from 'react';

export default (loader) => {
  class asynccomponent extends component {
    state = {
      component: null,
    };

    componentwillmount() {
      if (!this.state.component) {
        loader().then(module => {
          this.setstate({ component: module.default });
        });
      }
    }

    render() {
      const comp = this.state.component;
      return comp   :

loading

; } } return asynccomponent; } // router.js const home = asynccomponent(() => import('./route/home')); const login = asynccomponent(() => import('./route/login')); function routerconfig() { return ( ); } export default routerconfig;

使用 react-loadable

我们大可不必自己实现 asynccomponent,react-loadable 就是一个不错的选择。

import react from 'react';
import { browserrouter as router, switch, route } from 'react-router-dom';
import loadable from 'react-loadable';

const loadingcomp = () => loading;

const home = loadable({
  loader: () => import('./route/home'),
  loading: loadingcomp,
});
const login = loadable({
  loader: () => import('./route/login'),
  loading: loadingcomp,
});

function routerconfig() {
  return (
      
        
          
          
        
      
  );
}

export default routerconfig;

使用 react16.6 新特性

react 新发布的 16.6 版本提供了 lazy 方法和配套的 suspense 组件,用来处理异步渲染场景。通过新特性重新实现 asynccomponent:

import react, { lazy, suspense } from 'react';

export default (loader) => {
  const othercomponent = lazy(loader);

  const component = () => {
    return (
        loading...}>
          
        
    );
  };

  return component;
}

分割 redux 模块

这一部分主要参考 twitter 的做法。

一个 redux 模块包含 reducers、actions、action creators、state selectors。我们来创建一个 user redux 模块。

// model/user.js

const reducername = 'user';

const initialstate = {
  login: false,
  profile: {
    name: 'tom',
  },
};

const createactionname = name => `app/${reducername}/${name}`;

// actions
export const login_success = createactionname('login_success');
export const update_profile = createactionname('update_profile');

// action creators
export const updateprofile = payload => ({ payload, type: update_profile });

// reducer
export default function reducer(state = initialstate, action = {}) {
  switch (action.type) {
    case login_success:
      return { ...state, login: true };
    case update_profile:
      return { ...state, profile: action.payload };
    default:
      return state;
  }
}

// selectors
export const selectloginstate = state => state.login;
export const selectprofile = state => state.profile;

然后我们把 user model 和登录页面 connect 连接起来:

import react from 'react';
import { connect } from 'react-redux';

const login = ({ name }) => {
  return (
      

{`登录页面, name: ${name}`}

); }; const mapstatetoprops = ({ user }) => ({ name: user.profile.name, }); export default connect(mapstatetoprops)(login);

到目前为止,我们将登录页面和登录页面所需要用到的状态管理相关代码(user redux moudle)整合到了一次,这对于代码分割非常有利。

但是当我们将 reducer 添加到 redux store 时,就不是那么美好了。model 相关代码在一开始就会被加载进来,即使目前还没有页面会用到它。

运行时注册 reducer

通过 replacereducer 函数,我们可以在访问相应的页面时再添加 reducer 到 redux store 中。

实现 reducerregistry 并修改 createstore 的代码:

// model/reducerregistry
export class reducerregistry {
  constructor() {
    this._emitchange = null;
    this._reducers = {};
  }

  getreducers() {
    return { ...this._reducers };
  }

  register(name, reducer) {
    this._reducers = { ...this._reducers, [name]: reducer };
    if (this._emitchange) {
      this._emitchange(this.getreducers());
    }
  }

  setchangelistener(listener) {
    this._emitchange = listener;
  }
}

const reducerregistry = new reducerregistry();
export default reducerregistry;

// model/createstore
import { combinereducers, createstore } from 'redux';
import reducerregistry from './reducerregistry';

const initialstate = {};

const combine = (reducers) => {
  const reducernames = object.keys(reducers);
  // 维持仍为加载的 reducer 的初始化状态
  object.keys(initialstate).foreach(item => {
    if (reducernames.indexof(item) === -1) {
      reducers[item] = (state = null) => state;
    }
  });
  return combinereducers(reducers);
};

const reducer = combine(reducerregistry.getreducers());
const store = createstore(reducer, initialstate);

// 当一个新的 reducer 注册时,替换 store 的 reducer
reducerregistry.setchangelistener(reducers => {
  store.replacereducer(combine(reducers));
});

export default store;

现在,我们已经可以动态加载 reducer 了。接着,我们只需要在导入页面的同时注册相应的的 reducer 即可。

导入页面和模型

我们实现一个 dynamicwrapper,接受一个页面 loader 函数和模型名称数组。默认模型都在 model 目录下,且文件名和模型相对应。

import react, { lazy, suspense } from 'react';
import reducerregistry from './model/reducerregistry';

const asynccomponent = (loader) => {
  const othercomponent = lazy(loader);

  const component = () => {
    return (
        loading...}>
          
        
    );
  };

  return component;
};

export default (comploader, modelnames) => {
  const loader = () => {
    let modelloader;
    if (modelnames) {
      modelloader = () => modelnames.map((name) => import(`./model/${name}`));
    }

    const component = comploader();
    const models = modelloader  modelloader() : [];
    return promise.all([...models, component]).then(ret => {
      if (!models || !models.length) {
        return ret[0];
      } else {
        const len = models.length;
        ret.slice(0, len).foreach((m, index) => {
          m = m.default || m;
          reducerregistry.register(modelnames[index], m);
        });
        return ret[len];
      }
    });
  };

  return asynccomponent(loader);
}

使用起来也很简单,我们调整一下 router.js 中的代码:

const home = dynamicwrapper(() => import('./route/home'));
const login = dynamicwrapper(() => import('./route/login'), ['user']);

function routerconfig() {
  return (
      
        
          
            
            
          
        
      
  );
}

现在 model/user.js 并不会在一开始就加载,而是在访问登录页面时才会加载进来。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网