前所未有的快速!使用 Suspense/lazy,Webpack 4,Router 和 Redux 对 React App 进行代码拆分(Co...

栏目: 服务器 · 发布时间: 7年前

内容简介:小编推荐:

前所未有的快速!使用 Suspense/lazy,Webpack 4,Router 和 Redux 对 React App 进行代码拆分(Co...

前所未有的快速!使用 Suspense/lazy,Webpack 4,Router 和 Redux 对 React App 进行代码拆分(Co...

小编推荐: 掘金是一个面向 程序员 的高质量技术社区,从 一线大厂经验分享到前端开发最佳实践,无论是入门还是进阶,来掘金你不会错过前端开发的任何一个技术干货。

世界在不断变化,数字世界同样如此。 对于前端来说,我们的世界发展非常快。几周前 Facebook 团队为我们提供了一个全新的 React 版本,带来了很多新的东西。

今天我们将深入探讨 React v16.6.0 的新功能Suspenselazy ,并讨论最有趣的部分:如何将它们与 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 新引入的 schedulerconcurrent 等特性相结合,我们可以选择何时以及如何向用户提供交互元素:

  • 组件加载完成之前。
  • 组件加载完成后。
  • 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功能的 Suspenselazy 帮助下实现我们上面讨论的所有内容。

在本文中,我们将使用一个非常简单的计数器 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 中动态加载 HelloWorldStartCoding 组件。 我们可以使用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 和 在 HelloWorldStartCoding 路由的特定 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 中找到该问题的答案。

概括

前所未有的快速!使用 Suspense/lazy,Webpack 4,Router 和 Redux 对 React App 进行代码拆分(Co...

在这里你明白了! 我们有一个简单但很酷的 React-Redux 应用程序 ,它基于非常简单的 Webpack 4 配置和Suspense/lazy 动态组件加载逻辑来进行代码拆分。

您现在可以尝试实现新的路由,Reducers 以及 Async Redux-Saga集成! 它具有与本文中描述的相同的模式! 一定要让你的应用程序像你所看到的这样!

当然,如果我出错了或者有什么东西可以改进或简化,请告诉我。 PR 和提交 issues 都非常受欢迎! 这里有一些有用的链接:

英文原文: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...

以上所述就是小编给大家介绍的《前所未有的快速!使用 Suspense/lazy,Webpack 4,Router 和 Redux 对 React App 进行代码拆分(Co...》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Numerical Methods and Methods of Approximation in Science and En

Numerical Methods and Methods of Approximation in Science and En

Karan Surana / CRC Press / 2018-10-31

ABOUT THIS BOOK Numerical Methods and Methods of Approximation in Science and Engineering prepares students and other readers for advanced studies involving applied numerical and computational anal......一起来看看 《Numerical Methods and Methods of Approximation in Science and En》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具