React 和 Redux 的动态导入

图片描述

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!前端

代码分离与动态导入

对于大型 Web应用程序,代码组织很是重要。 它有助于建立高性能且易于理解的代码。 最简单的策略之一就是代码分离。 使用像 Webpack 这样的工具,能够将代码拆分红更小的部分,它们分为两个不一样的策略,静态和动态。react

经过静态代码分离,首先将应用程序的每一个不一样部分做为给定的入口点。 这容许 Webpack 在构建时将每一个入口点拆分为单独的包。 若是咱们知道咱们的应用程序的哪些部分将被浏览最多,这是完美的。webpack

动态导入使用的是 Webpackimport 方法来加载代码。因为 import 方法返回一个 promise,因此可使用async wait 来处理返回结果。git

// getComponent.js
async function getComponent() {
   const {default: module} = await import('../some-other-file')
   const element = document.createElement('div')
   element.innerHTML = module.render()
   return element
}

虽然这是一个很不天然的例子,可是能够看到这是一个多么简单的方法。经过使用 Webpack 来完成繁重的工做,咱们能够将应用程序分红不一样的模块。当用户点击应用程序的特定部分时,才加载咱们须要的代码。github

若是咱们将这种方法与 React 提供给咱们的控制结构相结合,咱们就能够经过延迟加载来进行代码分割。这容许咱们将代码的加载延迟到最后一分钟,从而减小初始页面加载。web

使用 React 处理延迟加载

为了导入咱们的模块,咱们须要决定应该使用什么 API。考虑到咱们使用 React 来渲染内容,让咱们从这里开始。redux

下面是一个使用 view 命名空间导出模块组件的简单API。segmentfault

// my-module.js
import * as React from 'react'

export default {
    view: () => <div>My Modules View</div>
}

如今咱们使用导入方法来加载这个文件,咱们能够很容易地访问模块的 view 组件,例如api

async function getComponent() {
    const {default} = await import('./my-module')
    return React.createElement(default.view)
})

然而,咱们仍然没有使用 React 中的方法来延迟加载模块。经过建立一个 LazyLoadModule 组件来实现这一点。该组件将负责解析和渲染给定模块的视图组件。promise

// lazyModule.js
import * as React from "react";

export class LazyLoadModule extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      module: null
    };
  }
 
  // after the initial render, wait for module to load
  async componentDidMount() {
    const { resolve } = this.props;
    const { default: module } = await resolve();
    this.setState({ module });
  }

  render() {
    const { module } = this.state;

    if (!module) return <div>Loading module...</div>;
    if (module.view) return React.createElement(module.view);

  }
}

如下是使用 LazyLoadModule 组件来加载模块的视图方式:

// my-app.js

import {LazyLoadModule} from './LazyLoadModule'

const MyApp = () => (
    <div className='App'>
        <h1>Hello</h1>
        <LazyLoadModule resolve={() => import('./modules/my-module')} />
    </div>
)

ReactDOM.render(<MyApp />, document.getElementById('root'))

下面是一个线上的示例,其中补充一些异常的处理。

https://codesandbox.io/embed/...

经过使用 React 来处理每一个模块的加载,咱们能够在应用程序的任什么时候间延迟加载组件,这包括嵌套模块。

使用 Redux

到目前为止,咱们已经演示了如何动态加载应用程序的模块。然而,咱们仍然须要在加载时将正确的数据输入到咱们的模块中。

让咱们来看看如何将 redux 存储链接到模块。 咱们已经经过公开每一个模块的视图组件为每一个模块建立了一个 API。 咱们能够经过暴露每一个模块的 reducer 来扩展它。 还须要公开一个名称,在该名称下咱们的模块状态将存在于应用程序的store 中。

// my-module.js
import * as React from 'react'
import {connect} from 'react-redux'

const mapStateToProps = (state) => ({
    foo: state['my-module'].foo,
})
const view = connect(mapStateToProps)(({foo}) => <div>{foo}</div>)

const fooReducer = (state = 'Some Stuff') => {
    return state
}
const reducers = {
    'foo': fooReducer,
}

export default {
    name: 'my-module',
    view,
    reducers,
}

上面的例子演示了咱们的模块如何得到它须要渲染的状态。

可是咱们须要先对咱们的 store 作更多的工做。咱们须要可以在模块加载时注册模块的 reducer。所以,当咱们的模块 dispatche 一个 action 时,咱们的 store 就会更新。咱们可使用 replaceReducer 方法来实现这一点。

首先,咱们须要添加两个额外的方法,registerDynamicModuleunregisterDynamicModule 到咱们的 store 中。

// store.js
import * as redux form 'redux'

const { createStore,  combineReducers } = redux

// export our createStore function
export default reducerMap => {
    
    const injectAsyncReducers = (store, name, reducers) => {
        // add our new reducers under the name we provide
        store.asyncReducers[name] = combineReducers(reducers);
        // replace all of the reducers in the store, including our new ones
        store.replaceReducer(
            combineReducers({
                ...reducerMap,
                ...store.asyncReducers
            })
        );
    };
    
    // create the initial store using the initial reducers that passed in
    const store = createStore(combineReducers(reducerMap));
    // create a namespace that will later be filled with new reducers
    store.asyncReducers = {};
    // add the method that will allow us to add new reducers under a given namespace
    store.registerDynamicModule = ({ name, reducers }) => {
        console.info(`Registering module reducers for ${name}`);
        injectAsyncReducers(store, name, reducers);
    };
    // add a method to unhook our reducers. This stops our reducer state from updating any more.
    store.unRegisterDynamicModule = name => {
        console.info(`Unregistering module reducers for ${name}`);
        const noopReducer = (state = {}) => state;
        injectAsyncReducers(store, name, noopReducer);
    };
    
    // return our augmented store object
    return store;
}

如你所见,代码自己很是简单。 咱们将两种新方法添加到咱们的 store 中。 而后,这些方法中的每一种都彻底取代了咱们 store 中的 reducer

如下是如何建立扩充 store

import createStore from './store'

const rootReducer = {
    foo: fooReducer
}

const store = createStore(rootReducer)

const App = () => (
    <Provider store={store}>
        ...
    </Provider>
)

接下来,须要更新 LazyLoadModule ,以便它能够在咱们的 store 中注册 reducer 模块。

咱们能够经过 props 获取 store。这很简单,但这意味着咱们每次都必须检索咱们的 store,这可能会致使 bug。记住这一点,让 LazyLoadModule 组件为咱们获取 store

react-redux <Provider /> 组件将 store 添加到上下文中时,只须要使用 contextTypesLazyLoadModule 中获取它。

// lazyModule.js 
export class LazyLoadModule extends React.component {
    ...
    async componentDidMount() {
        ...
        const {store} = this.context
    }
}

LazyLoadModule.contextTypes = {
    store: PropTypes.object,
}

如今能够从 LazyLoadModule 的任何实例访问咱们的 store。 剩下的惟一部分就是把 reducer 注册到 store 中。 记住,咱们是这样导出每一个模块:

// my-module.js
export default {
    name: 'my-module',
    view,
    reducers,
}

更新 LazyLoadModulecomponentDidMountcomponentWillUnmount 方法来注册和注销每一个模块:

// lazyModule.js 
export class LazyLoadModule extends React.component {
    ...
    async componentDidMount() {
        ...
        const { resolve } = this.props;
        const { default: module } = await resolve();
        const { name, reducers } = module;
        const { store } = this.context;
        if (name && store && reducers)
            store.registerDynamicModule({ name, reducers });
         this.setState({ module });
    }
    ...
    
    componentWillUnmount() {
        const { module } = this.state;
        const { store } = this.context;
        const { name } = module;
        if (store && name) store.unRegisterDynamicModule(name);
    }
}

线上示例以下:
https://codesandbox.io/s/znx1...

总结

经过使用 Webpack 的动态导入,咱们能够将代码分离添加到咱们的应用程序中。这意味着咱们的应用程序的每一个部分均可以注册本身的 components 和 reducers,这些 components 和 reducers将按需加载。此外,咱们还减小了包的大小和加载时间,这意味着每一个模块均可以看做是一个单独的应用程序。

原文:
https://codeburst.io/dynamic-...

你的点赞是我持续分享好东西的动力,欢迎点赞!

交流

干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,便可看到福利,你懂的。

clipboard.png

相关文章
相关标签/搜索