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

相關文章
相關標籤/搜索