想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!前端
对于大型 Web应用程序,代码组织很是重要。 它有助于建立高性能且易于理解的代码。 最简单的策略之一就是代码分离。 使用像 Webpack 这样的工具,能够将代码拆分红更小的部分,它们分为两个不一样的策略,静态和动态。react
经过静态代码分离,首先将应用程序的每一个不一样部分做为给定的入口点。 这容许 Webpack 在构建时将每一个入口点拆分为单独的包。 若是咱们知道咱们的应用程序的哪些部分将被浏览最多,这是完美的。webpack
动态导入使用的是 Webpack 的 import
方法来加载代码。因为 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
为了导入咱们的模块,咱们须要决定应该使用什么 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 存储链接到模块。 咱们已经经过公开每一个模块的视图组件为每一个模块建立了一个 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-...
你的点赞是我持续分享好东西的动力,欢迎点赞!
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
https://github.com/qq44924588...
我是小智,公众号「大迁世界」做者,对前端技术保持学习爱好者。我会常常分享本身所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,便可看到福利,你懂的。