内容简介:小编推荐:
小编推荐: 掘金是一个面向 程序员 的高质量技术社区,从 一线大厂经验分享到前端开发最佳实践,无论是入门还是进阶,来掘金你不会错过前端开发的任何一个技术干货。
世界在不断变化,数字世界同样如此。 对于前端来说,我们的世界发展非常快。几周前 Facebook 团队为我们提供了一个全新的 React 版本,带来了很多新的东西。
今天我们将深入探讨 React v16.6.0 的新功能 – Suspense 和 lazy ,并讨论最有趣的部分:如何将它们与 React 状态管理库 Redux 绑定。
在本文中,我们将使用已经创建的App,您可以从 GitHub repo https://github.com/BiosBoy/React-Redux-Suspense-Lazy-Memo-test 克隆
那是什么 – Suspense 和 lazy ?
简而言之, Suspense – 是一种功能,允许您延迟渲染应用程序树的一部分,直到满足某些条件(例如,终端或资源数据加载完成)。 关于 lazy – 它是动态导入的包装器,它来自最新的 React 版本。 因此,使用示例如下:
import React, {lazy, Suspense} from 'react'; const DynamicComponent = lazy(() => import('./someComponent')); function MyComponent() { return ( <Suspense fallback={<div>Loading...</div>}> <DynamicComponent /> </Suspense> ); }
这里我们有 someComponent
模块,必须异步加载。 因此,为了实现这一点,我们使用由 Suspense 包装的React lazy 功能。 这两个是一对甜蜜的情侣,必须始终在一起,因为第一个用于响应异步模块加载,第二个用于响应用户在屏幕上为用户提供一些微调器或加载信息,直到异步模块加载完成。 它更漂亮,简单实用。
为什么我们需要 Suspense / Lazy ?
从我们获得 动态导入(Dynamic Imports) 功能的那一刻到现在已有一年多了。 对于所有开发人员而言,这是一个巨大的飞跃,他们面临的问题是如何使应用程序更加轻量化,并且如何只向客户交付他们交互真正所需要那部分代码。
它引入了类似于 import
形式的新函数,适用于各种用例。 下面的函数返回一个所请求模块的 模块命名空间对象 的promise ,该对象是在获取、实例化或求值所有模块的依赖关系以及模块本身之后创建的。
以下是如何在原生的 JavaScript 中动态导入和使用某些模块:
<script type="module"> const moduleDynamic = './someModule.js'; import(moduleDynamic) .then((module) => { module.doSomething(); }); </script>
至于React开发,今天我们有很多针对 React 动态导入的自定义解决方案/库,旨在使我们的代码拆分工作轻松有趣。 在我看来,最受欢迎的是一个 React-Loadable 包,它为我们提供了一个友好的 API ,我们来看一下:
import React from 'react'; import Loadable from 'react-loadable'; const LoadableComponent = Loadable({ loader: () => import('./someComponent'), loading: <div>...Loading</div> }); class App extends React.Component { render() { return <LoadableComponent/>; } }
这很简单,不是吗? 对于我们所有人来说,这是一个非常好的方法,直到有了 React 原生支持的代码拆分,开箱即用。 因此,今天我认为 React-Loadable 将失去它的受欢迎程度,因为 Suspense/Lazy 为常规的 React 开发提供了更多的灵活性和定可定制性。
通过 React 新引入的 scheduler
和 concurrent
等特性相结合,我们可以选择何时以及如何向用户提供交互元素:
- 组件加载完成之前。
- 组件加载完成后。
- Onload 组件阶段。
不管怎样,这都需要自己的文章和示例,所以这里我们就不深入了。相反,有了以上所有信息,我们终于可以开始创建 React(Suspense/Lazy)-Redux-Router 应用程序了。
请记住,我们将使用已创建的示例作为测试,您可以克隆,并且在本文中使用它: https://github.com/BiosBoy/React-Redux-Suspense-Lazy-Memo-test
使用 Webpack 4 启动代码拆分(Code-splitting)
因此,第一步是创建正确的 Webpack 配置,使我们能够基于 App 的巨大 Modules(模块) /Routes(路由)创建一个应用程序包(在我们的例子中,最后一个是最重要的)。
我不会在这里复制一长串使用过的 Webpack 代码配置,相反,我只想向您提供主要部分,这是为我们将要创建的应用程序块而做出的响应,并对其进行描述。 在这里,我还将为您提供有关此配置的 Github gist 的链接,您可以在其中找到它的实现方式和工作方式……
在这里您可以找到准备工作中的整个 Webpack 4 配置: https://gist.github.com/BiosBoy/8b45ef3fec246813ecb05ce1ae11bfde
// ... const optimization = { optimization: { splitChunks: { chunks: ‘all’, minChunks: 2 }, // ...some other rules } // ... some other modules } // ...
如上所示, splitChunks
规则响应了 bundle / chunk 的创建。 这是配置 App bundles(打包)的一个要点。 它可以进行大量的定制,但是对于我们来说,当前的配置是详尽的。如果 app 中包含 2 个以上的动态模块/组件(在我们的例子中它们是路由),它将给我们一个创建块的机会。
有关 splitChunks
定制的更多信息,您可以在官方 Webpack 页面上找到: https://webpack.js.org/plugins/split-chunks-plugin/ 。
使用 React-Router 创建应用程序路由
在第二步,我们需要为我们的 App 创建正确的路由结构。 以下是我们将在React ^ 16功能的 Suspense 和 lazy 帮助下实现我们上面讨论的所有内容。
在本文中,我们将使用一个非常简单的计数器 App。 它将包括几个部分:
--| components: |-- <Header /> |-- <Body /> |-- <Footer /> --| container: |-- <AppContainer /> --| layout: |-- <AppLayout /> --| routes: |-- <HelloWorld /> |-- <StartCoding /> --| controller: |-- store.js |-- actions.js |-- reducers.js |-- initialState.js |--| middleware: |-- reduxLogger.js |-- rootReducer.js
… Body 组件是一个 HOC(高阶组件),可以接受 2 个 动态组件 : <HelloWorld />
和 <StartCoding />
。
重要! 我不会停留在 React 生态系统和组件导入的工作原理上(我想如果您正在阅读本文,这意味着您已经熟悉它了)。
我不会停止 React 生态系统和组件导入的工作方式(我想如果你正在阅读这篇文章,那就意味着你已经熟悉它了)。 我想向您展示的一点是,我们可以轻松地使用 Suspense/lazy,Webpack 4,Router 和 Redux 对 React App 进行代码拆分。
让我们开始工作,并建立我们的应用程序路由的 entry point(入口点) :
// ./container/index.js // ... some Components and dependencies imports const AppContainer = ({ store, history }) => { return ( <AppLayout> <Suspense fallback={<LoadingPlaceholder />}> <Switch location={location}> <Route exact path='/' render={ () => <AsyncComponent componentName='HelloWorld' /> } /> <Route path='/next' render={ () => <AsyncComponent componentName='StartCoding' /> } /> </Switch> </Suspense> </AppLayout> ); }; // ...
这里我们看到我们的主要的路由结构。 它由 react-router-dom
v.4 API 组成:
<Switcher /> <Route />
对我们来说最有趣是
-
<Suspense />
包装器,它提供了一个回退API,可以在动态组件最终加载之前向用户显示一些占位符。 这意味着,每当特定导航的 bundle(包) 未被用户路由加载时, Suspense 将回退以显示占位符。 这是简单而美观的 API,不是吗?:)
一旦 bundle(包) 被加载后, Suspense 将通过 <AppLayout />
App Layout 组件中的 Render Pattern抛出动态组件。
但这还不是全部。悬念只是动态加载的回退包装。我们不能忘记在应用程序中动态加载HelloWorld并开始对组件进行编码。我们可以使用React lazy wrapper实现这一点。在这里
但并非全部。 Suspense 只是动态加载的回退包装器。 我们不要忘记在 App 中动态加载 HelloWorld 和 StartCoding 组件。 我们可以使用React lazy 包装器来实现它。 这里就是:
// ./routes/index.js // ... some Components and dependencies imports const HelloWorld = lazy(() => import(/* webpackChunkName: "HelloWorld" */ './HelloWorld')); const StartCoding = lazy(() => import(/* webpackChunkName: "StartCoding" */ './StartCoding')); const Components = { HelloWorld, StartCoding }; const AsyncComponent = props => { const { componentName } = props; const Component = Components[componentName]; return <Component {...props} />; }; // ...
P.S. /* webpackChunkName: ‘COMPONENT_NAME’*/
允许我们在应用部署阶段设置 bundle(包) 名称,而不是常规编号。
就这样。现在我们可以将所有这些结合在一起并放入主要布局组件:
class AppLayout extends Component { render() { const { children } = this.props; return ( <div className={styles.appWrapper}> <Header /> {children} // here is our dynamic component will be <Footer /> </div> ); } }
使用异步 Reducers 创建 Redux 存储
第三步非常重要,在这里我们必须以某种方式使我们的 Redux 存储与动态组件兼容,并为每个存储提供自己的 reducers 存储。 今天这已经不是问题了。我将在这里展示一个非常流行的方法,基于 reducers 注入。下面是它的工作原理:
1)我们需要创建一个基本的 Redux 存储:
// ./controller/store.js // ... some Components and dependencies imports const rootStore = () => { const middleware = [routerMiddleware(history), logger]; const store = createStore( makeRootReducer(), initialState, compose( applyMiddleware(...middleware), ...enhancers ) ); store.asyncReducers = {}; return store; }; // ...
我知道代码很多:),但只有一个字符串对我们来说非常重要 – store.asyncReducers
。 Redux 存储中的这个注入对象将响应导入动态组件 reducer。
2)创建 App 的根reducer:
// ./controller/widdleware/rootReducer.js // ... some dependencies imports const makeRootReducer = asyncReducers => { return combineReducers({ ...asyncReducers, common, }); }; // ...
我们上面的内容 – 函数 makeRootReducer()
是 redux
包的 combineReducers
API 的常规包装器。 它可以接收任何数量的新 reducers(从动态组件获取),并在主存储区(main single store)将它们组合在一起。 但还没有结束。 我们需要一些 API 在 App 中使用这种方法。
因此,为了让它工作,我们可以使用 makeRootReducer
同一个文件中编写一个名为 injectReducer()
的小实用函数:
// ./controller/widdleware/rootReducer.js // ... some dependencies imports const makeRootReducer = asyncReducers => { // ... }; export const injectReducer = (store, { key, reducer }) => { if (Object.hasOwnProperty.call(store.asyncReducers, key)) return; store.asyncReducers[key] = reducer; store.replaceReducer(makeRootReducer(store.asyncReducers)); }; // ...
injectReducer
函数将检查动态加载的组件是否已经在存储中,如果 reducer 不存在,它将立即中断或添加它。
差不多就这些了!我们只需要在代码上做一些改进,我们的应用程序就会活跃起来!:)
将 Redux 存储与 React 动态组件集成
最后一步是对 Redux 存储进行集成,主要全局的 Reducer 和 在 HelloWorld
和 StartCoding
路由的特定 Reducers 之间。
在我上面提供的 App repo 中,您可以找到如何在组件路由和全局 App store之间实现业务逻辑。但是,我向您保证,在Redux 网站的例子中,没有什么有趣或创新的东西是找不到的
让我们继续,使我们的路线在整个 Redux 存储内进行 reducer 注入。 为了做到这一点,首先,我们需要升级当前路由的代码,并插入我们之前在组件加载之后创建的 injectReducer
函数:
// ./routes/index.js const AsyncComponent = props => { //... import(`./${componentName}/controller/reducer`) .then(({ default: reducer }) => { injectReducer(rootStore, { key: componentName, reducer }); }) //... }; // ...
注意弄清楚我们上面做了什么:
在这里,我们看到了常规的JS动态导入用法: import(
./${componentName}/controller/reducer )
…我们使用它来根据我们在渲染期间从 <AppLayout />
组件接收到的 componentName
prop 导入当前的组件 reducer 。
一旦模块 reducer 加载后,我们在全局 Redux rootStore
存储中注入它,并包含 key
prop 作为标记,用于检查该 reducer 是否已在存储中提供:
injectReducer(rootStore, { key: componentName, reducer });
因此,通过这种细微的集成,我们找到了一种简单的方法,可以在一个地方保存全局和特定路由的 reducer ,他们可以从整个 App 环境中访问。
使用 Redux 存储集成React-Router
我们需要实现的最后一件事就是让我们的 Redux 存储 响应位置/路由变化。 我们需要做的就是 – 在 redux
<Prodiver />
和 connected-react-router
<ConnectedRouter />
软件包 HOC 中包装 <AppContainer />
后代:
// ./container/index.js // ... some Components and dependencies imports const AppContainer = ({ store, history }) => { return ( <AppLayout> <Prodiver store={store}> <ConnectedRouter history={history}> //... dependencies Routes/Components </ConnectedRouter> </Provider> </AppLayout> ); }; // ...
此外,我们需要记住在全局 Redux 存储中添加带有 history
对象的 connected-react-router
reducer:
// ./controller/widdleware/rootReducer.js // ... some dependencies imports const makeRootReducer = asyncReducers => { return combineReducers({ ...asyncReducers, common, // routing router: connectRouter(history) }); }; // ...
如果您想知道如何获取 history
对象, connectRouter
reducer并将它们组合在一起,您可以在这篇文档的 App repo 中找到该问题的答案。
概括
在这里你明白了! 我们有一个简单但很酷的 React-Redux 应用程序 ,它基于非常简单的 Webpack 4 配置和Suspense/lazy 动态组件加载逻辑来进行代码拆分。
您现在可以尝试实现新的路由,Reducers 以及 Async Redux-Saga集成! 它具有与本文中描述的相同的模式! 一定要让你的应用程序像你所看到的这样!
当然,如果我出错了或者有什么东西可以改进或简化,请告诉我。 PR 和提交 issues 都非常受欢迎! 这里有一些有用的链接:
- 在Repo on Github上查看此应用程序 ;
- 链接到Github gist上的 Webpack 配置 ;
- 我个人使用的 Webpack4-React16-Redux4 App collector 来自这里 。
英文原文:https://medium.com/@svyat770/fast-as-never-before-code-splitting-with-react-suspense-lazy-router-redux-webpack-4-d55a95970d11
如果你觉得本文对你有帮助,那就请分享给更多的朋友
关注「前端干货精选」加星星,每天都能获取前端干货
以上所述就是小编给大家介绍的《前所未有的快速!使用 Suspense/lazy,Webpack 4,Router 和 Redux 对 React App 进行代码拆分(Co...》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 将Objective-C代码拆分为多个文件
- Vue 性能优化:如何实现延迟加载和代码拆分?
- 在react-router4中进行代码拆分的方法(基于webpack)
- Fundebug 前端 JavaScript 插件更新至 1.7.0,拆分录屏代码,还原部分 Script error.
- iOS组件化拆分之业务与拆分并行开发
- html – 当我们拆分表时,如何将div拆分为两列?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java 语言导学
Mary Campione Kalrath Alison Huml / 机械工业 / 2003-1 / 39.00元
《Java 语言导学(原书第3版)》既适合初学者,也适合有经验的程序员:新程序员通过从头到尾阅读《Java 语言导学(原书第3版)》可以得到最大的收获,包括按照第1章“起步”中的步骤说明编译和运行自己的第一个程序。有过程式语言(比如C)经验的程序员可能希望从Java编程语言的面向对象概念和特性开始学习。 有面向对象编程经验的程序员可能希望先学习更高级的内容。一起来看看 《Java 语言导学》 这本书的介绍吧!