React 和 Redux 的动态导入

36次阅读

共计 5239 个字符,预计需要花费 14 分钟才能阅读完成。

代码分离与动态导入
对于大型 Web 应用程序,代码组织非常重要。它有助于创建高性能且易于理解的代码。最简单的策略之一就是代码分离。使用像 Webpack 这样的工具,可以将代码拆分成更小的部分,它们分为两个不同的策略,静态和动态。
通过静态代码分离,首先将应用程序的每个不同部分作为给定的入口点。这允许 Webpack 在构建时将每个入口点拆分为单独的包。如果我们知道我们的应用程序的哪些部分将被浏览最多,这是完美的。
动态导入使用的是 Webpack 的 import 方法来加载代码。由于 import 方法返回一个 promise,所以可以使用 async wait 来处理返回结果。
// 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 来完成繁重的工作,我们可以将应用程序分成不同的模块。当用户点击应用程序的特定部分时,才加载我们需要的代码。
如果我们将这种方法与 React 提供给我们的控制结构相结合,我们就可以通过延迟加载来进行代码分割。这允许我们将代码的加载延迟到最后一分钟,从而减少初始页面加载。
使用 React 处理延迟加载
为了导入我们的模块,我们需要决定应该使用什么 API。考虑到我们使用 React 来渲染内容,让我们从这里开始。
下面是一个使用 view 命名空间导出模块组件的简单 API。
// my-module.js
import * as React from ‘react’

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

现在我们使用导入方法来加载这个文件,我们可以很容易地访问模块的 view 组件,例如
async function getComponent() {
const {default} = await import(‘./my-module’)
return React.createElement(default.view)
})

然而,我们仍然没有使用 React 中的方法来延迟加载模块。通过创建一个 LazyLoadModule 组件来实现这一点。该组件将负责解析和渲染给定模块的视图组件。
// 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 方法来实现这一点。
首先,我们需要添加两个额外的方法,registerDynamicModule 和 unregisterDynamicModule 到我们的 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 添加到上下文中时,只需要使用 contextTypes 在 LazyLoadModule 中获取它。
// 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,
}

更新 LazyLoadModule 的 componentDidMount 和 componentWillUnmount 方法来注册和注销每个模块:
// 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-…
你的点赞是我持续分享好东西的动力,欢迎点赞!
一个笨笨的码农,我的世界只能终身学习!
更多内容请关注公众号《大迁世界》!

正文完
 0