使用React和React Router进行代码拆分

Code splitting has gained popularity recently for its ability to allow you to split your app into separate bundles your users can progressively load. In this post we’ll take a look at not only what code splitting is and how to do it, but also how to implement it with React Router.

代码拆分最近因其允许您将应用程序拆分为用户可以逐步加载的单独捆绑包的能力而广受欢迎。 在这篇文章中,我们不仅会看到什么是代码拆分以及如何进行拆分,而且还将介绍如何使用React Router实现它。

Note that this article just one part of my comprehensive new React Router course.

请注意,本文只是我全面的新React Router课程的一部分

Also, I’ve created a video to go with this article:

另外,我还创建了一个视频,以配合本文:

It’s 2018. Your users shouldn’t have to download your entire app when all they need is a piece of it. If a user is creating a new post, it doesn’t make sense to have them download all the code for the Registration view. If a user is registering, they don’t need the huge rich text editor your app needs on the Settings view. It’s wasteful and some would argue disrespectful to those users who don’t have the privilege of unlimited bandwidth. This idea has not only gained much more popularity in recent years, but it’s also become exponentially easier to pull off — it even has a fancy cool name — code splitting.

现在是2018年。只要他们需要的一部分,您的用户就不必下载整个应用程序。 如果用户正在创建新帖子,则没有必要让他们下载“注册”视图的所有代码。 如果用户正在注册,则不需要在“设置”视图上使用您的应用所需的大型RTF编辑器。 这是浪费的,有些人会对那些没有无限带宽特权的用户不尊重。 这个想法不仅在最近几年变得越来越流行,而且也变得越来越容易实现-甚至有一个很酷的名字-代码拆分。

The idea is simple, don’t download code until the user needs it. In practice, it can be a little more complicated. The reason for this isn’t because code splitting itself is terribly difficult, but that there are various tools to do it and everyone has an opinion on which is the best. When you’re first starting out, it can be hard to parse what is what.

这个想法很简单,在用户需要之前不要下载代码。 实际上,它可能要复杂一些。 这样做的原因不是因为代码拆分本身非常困难,而是因为有各种各样的工具可以做到这一点,并且每个人都认为最好的是哪种。 刚开始时,可​​能很难解析什么。

The two most common approaches are using Webpack and its bundle loader or the ECMAScript dynamic import() proposal which is currently stage 3. Any chance I get to not use webpack, I take, so we’ll be using dynamic import() in this post.

两种最常见的方法是使用Webpack及其捆绑程序加载器或ECMAScript动态import()提议,该提议当前处于第3阶段 我有机会不使用webpack,所以在这篇文章中我们将使用动态import()

If you’re familiar with ES modules, you know that they’re completely static. What that means is that you must specific what you’re importing and exporting at compile time, not run time. This also means that you can’t dynamically import a module based on some condition. imports need to be declared at the top of your file or they’ll throw an error.

如果您熟悉ES模块,就会知道它们是完全静态的。 这意味着您必须在编译时而不是运行时指定要导入和导出的内容。 这也意味着您不能根据某些条件动态导入模块。 import s需要在文件顶部声明,否则将引发错误。

if (!user) {  import * as api from './api' // ?‍?. "import' and 'export' may only appear at the top level"}

Now, what if import didn’t have to be static? Meaning what if the code above worked? What benefits would that give us? First it would mean we could load certain modules on demand. That would be pretty powerful since it would enable us to get closer to the vision of only downloading code the user needs.

现在,如果有什么import 没有是静态的? 如果上面的代码有效,那意味着什么? 那会给我们带来什么好处? 首先,这意味着我们可以按需加载某些模块。 这将非常强大,因为它将使我们更接近仅下载用户所需代码的愿景。

if (editPost === true) {  import * as edit from './editpost'
edit.showEdtior()}

Assuming editpost contained a pretty large rich text editor, we’d make sure we didn’t download it until the user was actually ready to use it.

假设editpost包含一个非常大的RTF编辑器,我们将确保在用户实际准备使用它之前不下载它。

Another cool use case of this would be for legacy support. You could hold off on downloading certain code until you were certain the user’s browser didn’t already have it natively.

另一个很酷的用例是遗留支持。 您可以推迟下载某些代码,直到确定用户的浏览器本身没有该代码为止。

Here’s the good news (that I kind of already alluded to earlier). This type of functionality does exist, it’s supported by Create React App, and it’s currently in Stage 3 of the ECMAScript process. The difference is that instead of using import as you typically would, you use it like a function that returns you a promise that resolves with the module once the module is completely loaded.

这是个好消息(我早些时候已经提到过)。 确实存在这种功能,Create React App支持此功能,并且当前处于ECMAScript流程的第3阶段。 区别在于,它不是像通常那样使用import而是将其一个函数一样使用,该函数将向您返回一个承诺,该承诺将在模块完全加载后与模块一起解析。

if (editPost === true) {  import('./editpost')    .then((module) => module.showEditor())    .catch((e) => )}

Pretty rad, right?

漂亮拉德,对不对?

Now that we know how to dynamically import modules, the next step is figuring out how to use it with React and React Router.

现在我们知道了如何动态导入模块,下一步就是弄清楚如何在React和React Router中使用它。

The first (and probably biggest) question we need to ask ourselves when it comes to code splitting with React is where should we split at? Typically, there are two answers.

在使用React进行代码拆分时,我们需要问自己的第一个(也许是最大的)问题是我们应该在哪里拆分? 通常,有两个答案。

1) Split at the route level. ?
1)在路径层分割。 ?
2) Split at the component level. ?
2)在组件级别拆分。 ?

The more common approach is to split at the route level. You already split your app into different routes, so adding in code splitting on top of that feels pretty natural. How would this actually look?

更常见的方法是在路由级别进行拆分。 您已经将您的应用拆分为不同的路由,因此在此之上添加代码拆分感觉很自然。 这实际上看起来如何?

Let’s start off with a basic React Router example. We’ll have three routes, /, /topics, /settings.

让我们从一个基本的React Router示例开始。 我们将有3条路线//topics/settings

import React, { Component } from 'react'import {  BrowserRouter as Router,  Route,  Link,} from 'react-router-dom'
import Home from './Home'import Topics from './Topics'import Settings from './Settings'
class App extends Component {  render() {    return (      <Router>        <div>          <ul>            <li><Link to='/'>Home</Link></li>            <li><Link to='/topics'>Topics</Link></li>            <li><Link to='/settings'>Settings</Link></li>          </ul>
<hr />
<Route exact path='/' component={Home} />          <Route path='/topics' component={Topics} />          <Route path='/settings' component={Settings} />        </div>      </Router>    )  }}
export default App

Now, say our /settings route was super heavy. It contains a rich text editor, an original copy of Super Mario Brothers, and an HD image of Guy Fieri. We don’t want the user to have to download all of that when they’re not on the /settings route. Let’s use our knowledge of dynamic imports and React to code split the /settings route.

现在,假设我们的/settings路线非常繁琐。 它包含一个丰富的文本编辑器,一个超级马里奥兄弟的原始副本以及一个盖伊·费耶里的高清图像。 我们不希望用户不在/settings路由上时必须下载所有这些内容。 让我们利用我们对动态导入和React的知识来对/settings路由进行代码拆分。

Just like we solve any problem in React, let’s make a component. We’ll call it DynamicImport. The goal of DynamicImport is to dynamically load a module, then, once it’s loaded, to pass that module to its children.

就像我们解决React中的任何问题一样,我们来制作一个组件。 我们将其称为DynamicImportDynamicImport的目标是动态加载模块,然后在加载模块后将该模块传递给其children

const Settings = (props) => (  <DynamicImport load={() => import('./Settings')}>    {(Component) => Component === null      ? <Loading />      : <Component {...props} />}  </DynamicImport>)

The above code tells us two important details about DynamicImport. First, it will receive a load prop which when invoked, will dynamically import a module using the dynamic import syntax we covered earlier. Second, it will receive a function as its children which will need to be invoked with the imported module.

上面的代码告诉我们有关DynamicImport两个重要细节。 首先,它将收到一个load道具,当调用该道具时,它将使用我们前面介绍的动态导入语法动态导入模块。 其次,它将收到一个功能作为其children ,需要使用导入的模块对其进行调用。

Before we dive into the implementation of DynamicImport, let’s think about how we might accomplish this. The first thing we need to do is to make sure we call props.load. That will return us a promise that when it resolves, should have the module. Then, once we have the module, we need a way to cause a re-render so we can invoke props.children passing it that module. How do you cause a re-render in React? By setting state. By adding the dynamically imported module to DynamicImports local state, we follow the exact same process with React as we’re used to - fetch data -> set state -> re-render. Except this time instead of “fetch data”, we have “import module”.

在深入探讨DynamicImport的实现之前,让我们考虑一下如何实现此目的。 我们需要做的第一件事是确保我们调用props.load 。 这将给我们一个承诺,即在解决该问题时,应该拥有该模块。 然后,一旦有了模块,我们需要一种引起重新渲染的方法,以便我们可以调用通过该模块的props.children 。 您如何在React中引起重新渲染? 通过设置状态。 通过将动态导入的模块添加到DynamicImport的本地状态,我们对React遵循了完全相同的过程,即-获取数据->设置状态->重新渲染。 除了这次不是“获取数据”,我们还有“导入模块”。

First, let’s add some initial state to DynamicImport. component will eventually be the component that we’re dynamically importing.

首先,让我们向DynamicImport添加一些初始状态。 component最终将成为我们动态导入的组件。

class DynamicImport extends Component {  state = {    component: null  }}

Now, we need to call props.load. That will return us a promise that when it resolves, should have the module.

现在,我们需要调用props.load 。 这将给我们一个承诺,即在解决该问题时,应该拥有该模块。

class DynamicImport extends Component {  state = {    component: null  }  componentWillMount () {    this.props.load()      .then((component) => {        this.setState(() => ({          component        }))      })  }}

There’s one small gotcha here. If the module we’re dynamically importing is using ES modules (export default), it’ll have a .default property. If the module is using commonjs (module.exports), it won’t. Let’s change our code to adapt for that.

这里有一个小陷阱。 如果我们动态导入的模块使用ES模块(默认导出),则它将具有.default属性。 如果模块使用commonjs(module.exports),则不会。 让我们更改代码以适应这一点。

this.props.load()  .then((component) => {    this.setState(() => ({      component: component.default ? component.default : component    }))  })

Now that we’re dynamically importing the module and adding it to our state, the last thing we need to do it figure out what the render method looks like. If you’ll remember, when the DynamicImport component is used, it’ll look like this

现在我们正在动态导入模块并将其添加到状态,我们要做的最后一件事就是弄清楚render方法的外观。 如果您还记得的话,当使用DynamicImport组件时,它将看起来像这样

const Settings = (props) => (  <DynamicImport load={() => import('./Settings')}>    {(Component) => Component === null      ? <Loading />      : <Component {...props} />}  </DynamicImport>)

Notice that we’re passing it a function as it’s children prop. That means we need to invoke children passing is the component state.

请注意,我们正在为其传递一个函数,因为它是children prop。 这意味着我们需要调用children传递的component状态。

class DynamicImport extends Component {  state = {    component: null  }  componentWillMount () {    this.props.load()      .then((component) => {        this.setState(() => ({          component: component.default             ? component.default             : component        }))      })  }  render() {    return this.props.children(this.state.component)  }}

?. Solid. Now anytime we want to dynamically import a module, we can wrap it in DynamicImport. If we were to do this to all our routes, our code would look like this.

? 固体。 现在,只要我们想动态导入模块,就可以将其包装在D ynamicImport. 如果我们对所有路线都执行此操作,则代码将如下所示。

import React, { Component } from 'react'import {  BrowserRouter as Router,  Route,  Link,} from 'react-router-dom'
class DynamicImport extends Component {  state = {    component: null  }  componentWillMount () {    this.props.load()      .then((component) => {        this.setState(() => ({          component: component.default             ? component.default             : component        }))      })  }  render() {    return this.props.children(this.state.component)  }}
const Home = (props) => (  <DynamicImport load={() => import('./Home')}>    {(Component) => Component === null      ? <p>Loading</p>      : <Component {...props} />}  </DynamicImport>)
const Topics = (props) => (  <DynamicImport load={() => import('./Topics')}>    {(Component) => Component === null      ? <p>Loading</p>      : <Component {...props} />}  </DynamicImport>)
const Settings = (props) => (  <DynamicImport load={() => import('./Settings')}>    {(Component) => Component === null      ? <p>Loading</p>      : <Component {...props} />}  </DynamicImport>)
class App extends Component {  render() {    return (      <Router>        <div>          <ul>            <li><Link to='/'>Home</Link></li>            <li><Link to='/topics'>Topics</Link></li>            <li><Link to='/settings'>Settings</Link></li>          </ul>
<hr />
<Route exact path='/' component={Home} />          <Route path='/topics' component={Topics} />          <Route path='/settings' component={Settings} />        </div>      </Router>    )  }}
export default App

How we do we know this is actually working and code splitting our routes? If you run npm run build with an app created by Create React App, you’ll see our app’s been split.

我们怎么知道这实际上在起作用,并且代码拆分了我们的路线? 如果使用由Create React App创建的应用程序运行npm run build ,则会看到我们的应用程序已拆分。

Each chunk is each dynamic import() in our app.

每个chunk都是我们应用程序中的每个动态import()

You made it this far. Dance break ? ?‍?

您已经做到了。 跳舞休息?

Remember earlier when we talked about how there were two main ways to go about code splitting your app? We had this handy little guide.

还记得前面提到的两种主要方法来拆分应用程序的代码吗? 我们有这个方便的小指南。

1) Split at the route level. ?

1)在路径层分割。

2) Split at the component level. ?

2)在组件级别拆分。

So far we’ve only covered splitting at the route level. This is where a lot of people stop. Code splitting at the route level only is like brushing your teeth but never flossing. Your teeth will be mostly clean, but you’ll still get cavities.

到目前为止,我们仅介绍了在路线级别进行的拆分。 这是很多人停下来的地方。 仅在路由级别拆分代码就像刷牙,但从不使用牙线。 您的牙齿大部分会干净,但仍然会出现蛀牙。

Instead of thinking about code splitting as splitting your app up by its routes, you should think of it as splitting your app up by its components (<Route>s are just components, after all). If you have a rich text editor that lives in a modal, splitting by the route only will still load the editor even if the modal is never opened.

与其将代码拆分视为按其路径拆分应用程序,不如将其视为按其组件拆分应用程序(毕竟<Rou te>只是组件)。 如果您有一个处于模式中的RTF编辑器,则即使从未打开模式,仅按路径拆分仍会加载该编辑器。

At this point it’s more of a paradigm shift that needs to happen in your brain rather than any new knowledge. You already know how to dynamically import modules with import(), now you just need to figure out which components in your app you can hold off downloading until your user needs them.

在这一点上,更多的是需要在大脑中发生的范式转变,而不是任何新知识。 您已经知道如何使用import()动态导入模块,现在只需确定应用程序中的哪些组件可以推迟下载,直到用户需要它们为止。

It would be dumb of me to end this post without mentioning React Loadable. It’s a “higher order component for loading components with dynamic imports”. Essentially what it does is it takes everything we talked about in this post, and wraps it up into one nice little API. It even handles a bunch of edge cases we didn’t cover like error handling and server side rendering. Check it out if you want a simple, out of the box solution to code splitting.

结束这篇文章而不提到React Loadable会让我很愚蠢。 它是“用于通过动态导入加载组件的高阶组件”。 从本质上讲,它所做的是将我们在本文中讨论的所有内容都包含在内,并将其包装到一个不错的小API中。 它甚至可以处理很多我们没有涵盖的边缘情况,例如错误处理和服务器端渲染。 如果您需要简单的现成的代码拆分解决方案,请检查一下。

Follow me on Twitter — @tylermcginnis. And read more of my writing on tylermcginnis.com.

在Twitter上关注我- @tylermcginnis 并在tylermcginnis.com上阅读我的更多作品

翻译自: https://www.freecodecamp.org/news/code-splitting-with-react-and-react-router-62e174382d4c/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值