内容简介:小编推荐:
小编推荐: 掘金是一个面向 程序员 的高质量技术社区,从 一线大厂经验分享到前端开发最佳实践,无论是入门还是进阶,来掘金你不会错过前端开发的任何一个技术干货。
世界在不断变化,数字世界同样如此。 对于前端来说,我们的世界发展非常快。几周前 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拆分为两列?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
高中数学公式定律及要点透析
牛胜玉 编 / 2011-2 / 9.80元
《PASS绿卡图书:高中数学公式定律及要点透析(人教A版)(必修+选修)(第9次修订)》精选例句:时尚鲜活例句,再现巩固单词;延伸拓展:搭配用法辨析,提升运用能力;真题例句:精选真题例句,紧密联系高考。便于携带:三年教材词汇,方便随时记忆;附赠录音:用耳朵记单词,让学习零空隙。一起来看看 《高中数学公式定律及要点透析》 这本书的介绍吧!