react 前端拆分_对React进行代码拆分的简要介绍

react 前端拆分

A lot of folks (myself included) are used to write SPA React applications that need both a user-facing part and an admin interface for managing the data. In general, there are a variety of options that are commonly used to implement this type of requirements, which typically fall into two categories: 1). Two separate UIs (one public app and the other one only accessible by admins)2). Restrict the application pages/sections based on the users’ permissions.

许多人(包括我自己)被用来编写SPA React应用程序,这些应用程序既需要面向用户的部分,又需要管理界面来管理数据。 通常,有多种选项通常用于实现此类要求,通常分为两类:1)。 两个独立的用户界面(一个公共应用程序,另一个仅可由管理员访问)2)。 根据用户权限限制应用程序页面/部分。

I recently came across a situation while implementing a health care web application, where we had a bunch of UI components that were intended to be utilized from both medical personnel (admins) views as well as from the patients’ UI, so the second approach of the aforementioned ones made the most sense in that case.

最近,我在实施一个医疗保健Web应用程序时遇到了一种情况,那里有很多UI组件,这些组件打算从医务人员(管理员)视图以及患者的UI中使用,因此第二种方法是在这种情况下,上述方法最有意义。

One important issue we happen to have, however, was that even though we’ve got a lot of generic React components to be used across the entire app, a massive amount of them was not being rendered in all the pages our users were supposed to be interacting with, meaning that they’d end up downloading a bundle.js file containing lots of unneeded fragments of code. If a patient is simply filling out a form to schedule a doctor’s appointment, they won’t need the huge Google Maps component to locate the clinics they went to before, which might be only required by navigating to the /previousAppointments route for example.So the idea is quite simple, just don’t have the users download code until they need it.

然而,我们碰到的一个重要问题是,即使我们在整个应用程序中使用了许多通用的React组件,也并未在用户应该使用的所有页面中呈现大量组件与之交互,这意味着他们最终将下载一个bundle.js文件,其中包含许多不需要的代码片段。 如果患者只是填写表格以安排医生预约,那么他们将不需要庞大的Google Maps组件来定位以前去过的诊所,例如,仅需导航到/ previousAppointments路线即可。这个想法很简单,就是让用户在需要之前下载代码。

Now, you might be thinking that something like this would do the trick:

现在,您可能会认为这样的方法可以解决问题:

if (previousAppointments) {
  import Map from './components/Map';
}

If you’re familiar with JavaScript and ES modules it wouldn’t take you so long to figure out that the above naive approach is not going to work, since by definition the modules are static, and therefore we must declare what we’re importing/exporting at compile-time, not at run time. In other words, they may only be specified at the top level of the files and not in other places such as an if statement.

如果您熟悉JavaScript和ES模块 ,那么花很长时间您就不会发现上述幼稚的方法行不通,因为按照定义,这些模块是静态的,因此我们必须声明要导入的内容/在编译时导出,而不是在运行时导出。 换句话说,它们只能在文件的顶层指定,而不能在其他地方(如if语句)指定。

That being said, let’s pretend for a moment that the above conditionally imported module worked. Can you think of any benefits of doing so? Certainly, instead of having to load all the application modules/components up-front, we’d be allowed to import them on demand depending on what page our visitors are at any given time, so that’d be a reasonable approach towards only downloading code the user requires.

话虽如此,让我们假装一下上述有条件导入的模块正常工作。 您能想到这样做的好处吗? 当然,不必预先加载所有应用程序模块/组件,而是可以根据访问者在任何给定时间的页面上的需要,按需导入它们,因此这是仅下载的一种合理方法用户所需的代码。

Chances are you don’t care about IE11 anymore. But in our case, IE11 support was a must. So we concluded that if we ever find out a way to get the modules loaded conditionally, we might apply the same idea to execute legacy code only when the IE11 users landed on our web.

您可能不再关心IE11。 但就我们而言,必须支持IE11。 因此,我们得出的结论是,如果我们找到了一种有条件地加载模块的方法,那么只有当IE11用户登陆我们的网络时,我们才可以应用相同的思想来执行遗留代码。

动态EcmaScript模块可以拯救 (Dynamic EcmaScript modules to the rescue)

It turns out that there is a way to overcome the above-described issue: using the dynamic import() syntax. The difference with the normal import is that instead of using it as we’d normally do, we use it like a function that returns a promise — although import() looks like a function call, it is not actually, it’s a special syntax that just happens to use parentheses similar to super().Using this expression, we can load the module and return a promise that resolves into a module object that contains all of its exports. Getting back to our example, it would be something like:

事实证明,有一种方法可以克服上述问题:使用动态 import()语法。 普通导入的区别在于,它不是像通常那样使用它,而是返回一个Promise的函数那样使用它-尽管import()看起来像一个函数调用,但实际上并非如此,它是一种特殊的语法,恰好使用与super()类似的括号。使用此表达式,我们可以加载模块并返回一个承诺,该承诺会解析为包含其所有导出内容的模块对象。 回到我们的示例,它类似于:

if (previousAppointments) {
  import('./components/Map')
    .then(module => console.log(module)) // do something with the module
    .catch(e => console.error(e));
}

We can even use await if the import statement were inside an async function:

如果import语句在异步函数中,我们甚至可以使用await

const module = await import('./components/Map');

One thing to be aware of, though, is that this syntax is not officially added to the EcmaScript specification yet, and at the time of this writing it’s in stage 4 of the process. If you’re using Create React App it’s already supported by default, otherwise, you just need to install a babel plugin named plugin-syntax-dynamic-import

但是要注意的一件事是,该语法尚未正式添加到EcmaScript规范中,而在撰写本文时,它处于该过程的第4阶段 。 如果您使用的是Create React App,则默认情况下已经支持它,否则,您只需要安装一个名为plugin-syntax-dynamic-import的babel插件即可

npm install — save-dev @babel/plugin-syntax-dynamic-import

and then use it in the .babelrc configuration file:

然后在.babelrc配置文件中使用它:

{ "plugins": ["@babel/plugin-syntax-dynamic-import"] }

OK so now that we all know how to import modules dynamically, you might be wondering how to use this knowledge with React, and consequently, where should we split our application at? Well, there are a couple of approaches to address this question:

好的,现在我们都知道如何动态导入模块,您可能想知道如何将这些知识与React一起使用,因此,我们应该在哪里拆分应用程序? 好了,有两种方法可以解决这个问题:

  1. Route level splitting

    路线等级分割
  2. Component level splitting

    组件级拆分

路线等级分割 (Route level splitting)

This is the most popular way when it comes to code splitting our app. Assuming you have some experience with any kind of routing tools, this should feel pretty natural after all, isn’t it? So this is where the React.lazy function comes into play. It accepts a function which must call the dynamic import, and returns a promise which resolves to a module with a default export containing a React component. This means that we don’t need to render the lazily loaded component by ourselves, instead, we render what React.lazy returns and it’ll take care of actually rendering the component.Let’s take a look at how this would look like — I’m using reach-router instead of the typical React Router, but it shouldn’t make any difference for this example:

当涉及到代码拆分我们的应用程序时,这是最流行的方式。 假设您对任何一种路由工具都有一定的经验,那毕竟应该很自然,不是吗? 这就是React.lazy 功能发挥作用。 它接受一个必须调用动态导入的函数,并返回一个promise,该promise解析为带有默认导出的模块,该模块包含React组件。 这意味着我们不需要自己渲染延迟加载的组件,而是渲染React.lazy返回的内容,它会照顾到实际渲染的组件。让我们看一下它的外观-我正在使用到达路由器而不是典型的React Router,但对于本示例来说,它应该没有任何区别:

import React from 'react';
import { Location, Router } from '@reach/router';


import Home from './Home';
import AppointmentForm from './AppointmentForm';
const PreviousAppointments = React.lazy(() => import('./components/PreviousAppointments'));


class App extends React.Component {
  render() {
    return (
      <Location>
        {({ location }) => (
          <Router location={location}>
            <Home path="/" />
            <AppointmentForm path="/newAppointment" />
            <PreviousAppointments path="/previousAppointments" />
          </Router>
        )}
      </Location>
    );
  }
}

And that’s all that it takes to code split our app at the route level. In the snippet, we’re just dynamically importing the PreviousAppointments route component, but we can also use the same approach on the other two routes if we wanted to. Below is the output when we build our app — yarn run build or npm run build depending on what the package manager of your choice is.

这就是在路由级别进行代码拆分我们的应用程序所需的全部。 在代码段中,我们只是动态导入PreviousAppointments路由组件,但是如果需要,我们还可以在其他两条路由上使用相同的方法。 下面是我们构建应用程序时的输出- yarn run buildnpm run build具体取决于您选择的软件包管理器。

Asset                 Size       Chunksapp.91ac87d5.js       114  KiB   0 [emitted] 
1.19z8duj4.chunk.js 7.97 KiB 1 [emitted]
vendors.j8z7cn1q.js 869 KiB 2 [emitted]

You’ll get one chunk file per each dynamic import() in the app.

应用程序中的每个动态import()都会获得一个chunk文件。

What if we wanted to show some content to the users while the components are being loaded dynamically? That’s what the React.Suspense component is used for. Anytime we want to import dynamic components we can wrap them all into a Suspense instance and pass it a fallback prop which is a React element that is rendered until all of its children’s components get successfully loaded. Now let’s give it a shot at Suspense by turning all of our routes into dynamic components and passing it a <Loading /> element as a fallback:

如果我们想在动态加载组件时向用户显示一些内容怎么办? 这就是React.Suspense组件的用途。 每当我们要导入动态组件时,我们都可以将它们全部包装到Suspense实例中,并传递给它一个fallback属性,该属性是一个React元素, 直到所有子组件都被成功加载为止 。 现在,通过将所有路线转换为动态组件并将其传递给<Loading />元素作为后备,让我们来看看Suspense:

import React, { Suspense } from 'react';
import { Location, Router } from '@reach/router';


import Loading from './components/Loading';


const Home = React.lazy(() => import('./components/Home'));
const AppointmentForm = React.lazy(() => import('./components/AppointmentForm'));
const PreviousAppointments = React.lazy(() => import('./components/PreviousAppointments'));


class App extends React.Component {
  render() {
    return (
      <Location>
        {({ location }) => (
          <Suspense fallback={<Loading />}>
            <Router location={location}>
              <Home path="/" />
              <AppointmentForm path="/newAppointment" />
              <PreviousAppointments path="/previousAppointments" />
            </Router>
          </Suspense>
        )}
      </Location>
    );
  }
}

组件级拆分 (Component level splitting)

Code splitting your application at the routes level might represent a significant improvement in terms of the size of files that webpack builds out, so you may consider it as good enough in a few cases. However, there is still a more granular way of dynamically importing code in React — at the Component level.Inside a single component, you might need to render a child component based on a certain state. Assuming our Map component is tremendously heavy, we can get it loaded if and only if it’s necessary — so we’d first show a button to the users and if they want to get the Map functionality in place on the current page then they can click on it to load the component based on that action.At a glance, it might sound a little bit tricky to achieve, but in practice, it’s pretty straightforward:

在路由级别拆分应用程序的代码可能表示Webpack生成的文件大小有了显着改善,因此在某些情况下您可能认为它足够好。 但是,仍然存在一种更细粒度的方法来在React中动态导入代码-在组件级别。在单个组件内,您可能需要根据特定状态呈现子组件。 假设我们的Map组件非常繁重, 则仅在必要时才可以加载它-因此,我们首先向用户显示一个按钮,如果他们想在当前页面上使用Map功能,那么他们可以单击乍一看,听起来可能有点棘手,但是实际上,这非常简单:

class PreviousAppointments extends Component {
  constructor(props) {
    super(props);


    this.state = { map: null };


    this.handleClick = this.handleClick.bind(this);
  }


  handleClick() {
    import('./components/Map').then(module =>
      this.setState(() => ({
        map: module.default,
      })),
    );
  }


  render() {
    const { map: Map } = this.state;


    return (
      <>
        <div>Previous Appointments</div>
        ...
        {Map ? <Map /> : <button onClick={this.handleClick}>Show Map</button>}
      </>
    );
  }
}

As you might have seen, there’s one small caveat in the above code.If the module we’re dynamically importing is using ES modules (export default), it’ll have a .default property; this is only the case for ES modules, meaning that if the module is using commonjs (module.exports), it won’t.

如您所见,上面的代码中有一个小警告:如果我们动态导入的模块使用ES模块(默认导出),则它将具有.default属性; 这仅适用于ES模块,这意味着如果该模块使用commonjs(module.exports),则不会。

Besides the dynamic import() proposal, another common approach to performing code splitting in client-side JavaScript is using the require.ensure method from webpack, but as stated in their docs the import() syntax should be preferred.

除了动态import()提议之外,在客户端JavaScript中执行代码拆分的另一种常见方法是使用require.ensure方法,但是正如他们的文档中所述, import()语法应该是首选。

最后的想法 (Final thoughts)

A big gain we got in our application was the fact that we managed to optimize the bundles’ sizes since each of them ended up including only the sufficient modules/components depending on the type of users coming through the portal.

我们在应用程序中获得的一大收获是我们设法优化了捆绑软件的大小,因为每个捆绑软件最终仅包含足够的模块/组件,具体取决于通过门户网站的用户类型。

React lazy and Suspense is definitely not a good fit if you’re using server-side rendering (SSR) in your project as this is not supported yet — I believe that might be one of the primary reasons for which those are still experimental features and are not included as part of the stable version of React. For SSR needs, React recommends a library named Loadable Components. It has a nice guide for bundle splitting with server-side rendering.

如果您在项目中使用服务器端渲染(SSR),则ReactlazySuspense绝对不是一个很好的选择,因为尚不支持此功能-我认为这可能仍是这些仍是实验性功能的主要原因之一,并且不包含在React的稳定版本中。 为了满足SSR的需求,React建议使用一个名为Loadable Components的库。 对于使用服务器端渲染进行捆绑包拆分,它提供了很好的指南

Another advantage of using code splitting was the ability to exclude administrative code for regular users to hide potentially sensitive data, which is a must while developing health care applications.

使用代码拆分的另一个优点是能够排除常规用户的管理代码以隐藏潜在的敏感数据,这在开发医疗保健应用程序时是必须的。

Last but not least, be careful and use dynamic imports only when it makes sense. The static form is preferable for loading initial dependencies and can benefit more readily from static analysis tools and tree shaking.

最后但并非最不重要的一点是,要小心并仅在有意义时才使用动态导入。 静态形式对于加载初始依赖项是更可取的,并且可以更容易地从静态分析工具和摇树中受益。

If you’ve worked with React before, I hope this helped get you started with when and how to use the code splitting optimization technique in your applications for the sake of speeding up the performance and therefore better user experience.

如果您曾经使用过React,那么我希望这可以帮助您开始何时以及如何在应用程序中使用代码拆分优化技术,以加快性能并从而改善用户体验。

Thanks for reading and happy coding!

感谢您的阅读和愉快的编码!

翻译自: https://blog.kommit.co/a-gentle-introduction-to-code-splitting-with-react-395ddf44b71b

react 前端拆分

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值