react引入多个图片_重新引入React:v16之后的每个React更新都已揭开神秘面纱。

react引入多个图片

In this article (and accompanying book), unlike any you may have come across before, I will deliver funny, unfeigned and dead serious comic strips about every React update since v16+. It’ll be hilarious, either intentionally or unintentionally, easy on beginners as well as professionals, and will be very informative as a whole.

在本文(和随附的书)中,与您之前可能遇到的任何内容不同,我将针对v16 +以后的每个React更新发布有趣,虚假且死气沉沉的漫画。 无论是有意还是无意,它都会很有趣,无论对于初学者还是专业人士来说都很容易,并且从整体上来说将非常有用。

为什么选择漫画? (Why Comic Strips ?)

I have been writing software for over 5 years. But I have done other things, too. I’ve been a graphics designer, a published author, teacher, and a long, long time ago, an amateur Illustrator.

我从事软件开发已有5年以上。 但是我也做了其他事情。 我曾经是图形设计师,出版作家,老师,很久以前是业余Illustrator。

I love the tech community, but sometimes as a group, we tend to be a little narrow-minded.

我热爱技术社区,但有时作为一个整体,我们往往心胸狭窄。

When people attempt to teach new technical concepts, they forget who they were before they became developers and just churn out a lot of technical jargon — like other developers they’ve seen.

当人们尝试教授新的技术概念时,他们在成为开发人员之前就忘记了自己是谁,而只是产生了很多技术术语-就像他们所见过的其他开发人员一样。

When you get to know people, it turns out so many of us have different diverse backgrounds! “If you were a comedian, why not explain technical concepts with some comedy?

当您认识人时,事实证明我们很多人都有不同的背景! “如果您是喜剧演员,为什么不跟一些喜剧解释技术概念呢?

Wouldn’t that be so cool?

那不是很酷吗?

I want to show how we can become better as engineers, as teams, and as a community, by openly being our full, weird selves, and teaching others with all that personality. But instead of just talking, I want to make it noteworthy and lead by example. So, you’re welcome to my rendition of a comic strip inspired book about every React update since v16.

我想展示如何通过公开地成为我们充实,怪异的自我,并以所有这些个性来教导他人,从而使我们作为工程师,团队和社区变得更好 但是,我不仅要讲话,还应引起关注并以身作则。 因此,欢迎您阅读我翻译自v16以来每本React更新的漫画启发书。

With recently released v16.8 features, there’s going to be a lot of informative comic strips to be delivered!

借助最近发布的v16.8功能,将提供大量内容丰富的漫画!

Inspired by Jani Eväkallio.

JaniEväkallio启发。

This is a very interesting but long read. Please download the ebook (PDF, Epub & Mobi) absolutely free — without having to share your email with me. You can also pay whatever you want for the book if you wanna support my work.

这是一个非常有趣但很长的阅读。 绝对免费 下载电子书 (PDF,Epub和Mobi),而无需与我分享您的电子邮件。 如果您想支持我的工作,也可以支付所需的书本费用。

如何阅读这篇文章 (How to Read this Article)

First, get the ebook. Apart from being able to read offline, the ebooks have syntax highlighted codes that make them easier to read as well. Go get it.

首先, 获取电子书 。 除了可以脱机阅读之外,电子书还具有语法突出显示的代码,这些代码也使它们更易于阅读。 去得到它

Secondly, please find the associated code repository for the book on Github. This will help you follow along with the examples allowing for more hands-on practice.

其次,请在Github上找到该书的相关代码存储库 。 这将帮助您遵循允许更多动手实践的示例。

为什么要重新引入React? (Why Reintroduce React?)

I wrote my first React application 3 to 4 years ago. Between then and now, the fundamental principles of React have remained the same. React is just as declarative and component-based today as it was then.

3到4年前,我写了我的第一个React应用程序。 从那时到现在,React的基本原理一直保持不变。 今天,React和当时一样,都是声明式的和基于组件的。

That’s great news, however, the way we write React applications today has changed!

这真是个好消息,但是,我们今天编写React应用程序的方式已经改变了!

There’s been a lot of new additions (and well, removals).

有很多新的添加(以及删除)。

If you learned React a while back it’s not impossible that you haven’t been up to date with every new feature/release. It’s also possible to get lost on all the new features. Where exactly do you start? How important are they for your day to day use?

如果您有一阵子学会了React,那么并非没有最新的功能。 还可能会迷失所有新功能。 您到底从哪里开始? 它们对于您的日常使用有多重要?

Even as an experienced engineer, I sometimes find unlearning old concepts and relearning new ones just as intimidating as learning a new concept from the scratch.

即使是经验丰富的工程师,我有时也会发现学习旧概念并重新学习新概念,就像从头开始学习新概念一样令人生畏。

If that’s the case with you, I hope I can provide the right help for you via this guide.

如果您的情况如此,希望我能通过本指南为您提供正确的帮助。

The same applies if you’re just learning React.

如果您只是在学习React,也是如此。

There’s a lot of state information out there. If you learn React with some old resource, yes, you’ll learn the fundamentals of React, but modern React has new interesting features that are worth your attention. It’s best to know those now, and not have to unlearn and relearn newer concepts.

那里有很多状态信息。 如果您使用一些旧资源学习React,是的,您将学习React的基础知识,但是现代React具有值得关注的新有趣功能。 最好现在就知道这些,而不必学习和重新学习较新的概念。

Whether you’ve been writing React for a while, or new to developing React applications, I will be discussing every update to React since version 16.

无论您是编写React已有一段时间还是开发React应用程序的新手,我都将讨论自版本16起的每个 React更新。

This will keep you in sync with the recent changes to React, and help you write better software.

这将使您与对React的最新更改保持同步,并帮助您编写更好的软件。

Remember, a reintroduction to React is important for not just beginners, but professionals alike. It all depends on how well you’ve kept your ear to the ground, and really studied the many changes that have been released over the last 12 months.

记住,重新介绍React不仅对初学者很重要,对专业人士也很重要。 这完全取决于您对地面的了解程度,并真正研究了过去12个月中发布的许多更改。

On the bright side, I’m bringing you a one-stop reference to all the changes.

从好的方面来说,我为您提供一站式参考所有更改。

In this book, I’ll take you on a journey — alongside some humour and unique content to follow.

在本书中,我将带您探索旅程-并附有一些幽默和独特的内容。

Ready?

准备?

自版本16起发生了什么变化? (What’s Changed since version 16?)

If you think not much has changed, think again.

如果您认为变化不大,请再考虑一下。

Here’s a list of the relevant changes we’ll be discussing in this guide:

以下是我们将在本指南中讨论的相关更改的列表:

  • New Lifecycle Methods.

    新的生命周期方法。
  • Simpler State Management with the Context API.

    使用Context API简化状态管理。
  • ContextType — Using Context without a Consumer.

    ContextType —在没有使用者的情况下使用上下文。
  • The Profiler: Using Charts and Interactions.

    探查器:使用图表和交互。
  • Lazy Loading with React.Lazy and Suspense.

    使用React.Lazy和Suspense进行延迟加载。
  • Functional PureComponent with React.memo

    具有React.memo的功能性PureComponent
  • Simplifying React apps with Hooks!

    使用Hooks简化React应用程序!
  • Advanced React Component Patterns with Hooks.

    带钩子的高级React组件模式

It goes without saying that a lot has been introduced since version 16. For ease, each of these topics have been broken down into separate sections.

不用说,自版本16开始引入了很多内容。为简便起见,将这些主题中的每一个都细分为单独的部分。

In the next section I’ll begin to discuss these changes by having a look at the new lifecycle methods available from version 16.

在下一节中,我将通过查看版本16中提供的新生命周期方法来开始讨论这些更改。

第1章:新的生命周期方法。 (Chapter 1: New Lifecycle Methods.)

He’s been writing software for a while, but new to the React ecosystem.

他已经写了一段时间的软件,但是对React生态系统还是陌生的。

Meet John.

见约翰。

For a long time he didn’t fully understand what lifecycle truly meant in the context of React apps.

很长一段时间以来,他还没有完全理解生命周期在React应用程序中的真正含义。

When you think of lifecycle what comes to mind?

当您想到生命周期时,会想到什么?

生命周期到底是什么? (What’s Lifecycle Anyway?)

Well, consider humans.

好吧,考虑人类。

The typical lifecycle for a human is something like, “child” to “adult” to “elderly”.

对于人类来说,典型的生命周期是:“孩子”到“成人”到“老人”。

In the biological sense, lifecycle refers to the series of “changes in form” an organism undergoes.

从生物学意义上讲,生命周期是指生物体经历的一系列“形式变化”。

The same applies to React components. They undergo a series of “changes in form”.

这同样适用于React组件。 他们经历了一系列的“形式变化”。

Here’s what a simple graphical representation for React components would be.

这是React组件的简单图形表示。

The four essential phases or lifecycle attributed to a React component include:

归因于React组件的四个基本阶段或生命周期包括:

  • Mounting — like the birth of a child, at this phase the component is created (your code, and react’s internals) then inserted into the DOM

    安装 -就像孩子的出生一样,在此阶段创建组件(您的代码和react的内部构件),然后将其插入DOM

  • Updating — like humans “grow”, in this phase a React component undergoes growth by being updated via changes in props or state.

    更新 -就像人类“成长”一样,在这一阶段,React组件通过道具或状态的变化进行更新,从而实现了增长。

  • Unmounting — Like the death of a human, this is the phase the component is removed from the DOM.

    卸载 -就像人类的死亡一样,这是从DOM中删除组件的阶段。

  • Error Handling — Think of this as being comparable to when humans fall sick and visit the doctor. Sometimes your code doesn’t run or there’s a bug somewhere. When this happens, the component is in the error handling phase. I intentionally skipped this phase in the illustration earlier.

    错误处理 -认为这与人类生病并去看医生时的情况可比。 有时您的代码无法运行,或者某个地方存在错误。 发生这种情况时,组件处于错误处理阶段。 我在前面的图中有意跳过了这一阶段。

生命周期方法。 (Lifecycle Methods.)

Now that you understand what lifecycle means, what are “lifecycle methods”?

现在您了解了生命周期的含义,什么是“生命周期方法”?

Knowing the phases /lifecycle a React component goes through is one part of the equation. The other part is understanding the methods React makes available (or invokes) at each phase.

了解React组件经历的阶段/生命周期是方程式的一部分。 另一部分是了解React在每个阶段都可以使用(或调用)的方法。

The methods invoked during different phase/lifecycle of a component is what’s popularly known as the component lifecycle methods e.g. In the mounting and updating phases, the render lifecycle method is always invoked.

在组件的不同阶段/生命周期期间调用的方法是众所周知的组件生命周期方法,例如,在安装和更新阶段,始终调用render生命周期方法。

There are lifecycle methods available on all 4 phases of a component — mounting, updating, unmounting and error handling.

组件的所有四个阶段都有可用的生命周期方法-安装,更新,卸载和错误处理。

Knowing when a lifecycle method is invoked (i.e the associated lifecycle/phase) means you can go ahead to write related logic within the method and know it’ll be invoked at the right time.

知道何时调用生命周期方法(即相关联的生命周期/阶段)意味着您可以继续在该方法中编写相关的逻辑,并知道它将在正确的时间被调用。

With the basics out of the way, let’s have a look at the actual new lifecycle methods available from version 16.

在不了解基础知识的情况下,让我们看一下版本16中可用的实际新生命周期方法。

静态getDerivedStateFromProps。 (static getDerivedStateFromProps.)

Before explaining how this lifecycle method works, let me show you how the method is used.

在解释此生命周期方法如何工作之前,让我向您展示如何使用该方法。

The basic structure looks like this:

基本结构如下所示:

const MyComponent extends React.Component {  ...
static getDerivedStateFromProps() {     //do stuff here  }  }

The method takes in props and state:

该方法接受propsstate

...
static getDerivedStateFromProps(props, state) {	//do stuff here  }
...

And you can either return an object to update the state of the component:

您可以返回一个对象来更新组件的状态:

...
static getDerivedStateFromProps(props, state) {      return {     	points: 200 // update state with this     }  }
...

Or return null to make no updates:

或返回null以不进行任何更新:

...
static getDerivedStateFromProps(props, state) {    return null  }
...

I know what you’re thinking. Why exactly is this lifecycle method important?

我知道你在想什么 为什么这种生命周期方法到底很重要?

Well, it is one of the rarely used lifecycle methods, but it comes in handy in certain scenarios.

嗯,它是很少使用的生命周期方法之一,但是在某些情况下派上用场。

Firstly, this method is called (or invoked) before the component is rendered to the DOM on initial mount.

首先,在初始安装时将组件呈现给DOM 之前,将调用(或调用)此方法。

Below’s a quick example.

下面是一个简单的示例。

Consider a simple component that renders the number of points scored by a football team.

考虑一个简单的组件,该组件可以呈现足球队得分的数量。

As you may have expected, the number of points is stored in the component state object:

如您所料,点数存储在组件状态对象中:

class App extends Component {  state = {    points: 10  }  render() {    return (      <div className="App">        <header className="App-header">          <img src={logo} className="App-logo" alt="logo" />          <p>            You've scored {this.state.points} points.          </p>        </header>      </div>    );  }}

Note that the text reads, you have scored 10 points — where 10 is the number of points in the state object.

请注意,文本显示为, 您得分10分 -其中10是状态对象中的点数。

Just an as an example, if you put in the static getDerivedStateFromProps method as shown below, what number of points will be rendered?

仅作为示例,如果您按如下所示放入静态的getDerivedStateFromProps方法,将渲染多少个点?

class App extends Component {  state = {    points: 10  }	  // *******  //  NB: Not the recommended way to use this method. Just an example. Unconditionally overriding state here is generally considered a bad idea  // ********  static getDerivedStateFromProps(props, state) {    return {      points: 1000    }  }  render() {    return (      <div className="App">        <header className="App-header">          <img src={logo} className="App-logo" alt="logo" />          <p>            You've scored {this.state.points} points.          </p>        </header>      </div>    );  }}

Right now, we have the static getDerivedStateFromProps component lifecycle method in there. If you remember from the previous explanation, this method is called before the component is mounted to the DOM. By returning an object, we update the state of the component before it is even rendered.

现在,我们有了静态的getDerivedStateFromProps组件生命周期方法。 如果您还记得前面的解释,则在将组件安装到DOM之前调用此方法。 通过返回一个对象,我们甚至在呈现组件之前就更新了组件的状态。

And here’s what we get:

这就是我们得到的:

With the 1000 coming from updating state within the static getDerivedStateFromProps method.

随着1000来自static getDerivedStateFromProps方法中的更新状态。

Well, this example is contrived, and not really the way you’d use the static getDerivedStateFromProps method. I just wanted to make sure you understood the basics first.

好吧,这个示例是人为设计的,而不是您真正使用static getDerivedStateFromProps方法的方式。 我只是想确保您首先了解基础知识。

With this lifecycle method, just because you can update state doesn’t mean you should go ahead and do this. There are specific use cases for the static getDerivedStateFromProps method, or you’ll be solving a problem with the wrong tool.

使用这种生命周期方法,仅因为您可以更新状态并不意味着您应该继续执行此操作。 static getDerivedStateFromProps方法有特定的用例,否则您将使用错误的工具解决问题。

So when should you use the static getDerivedStateFromProps lifecycle method?

那么什么时候应该使用static getDerivedStateFromProps生命周期方法?

The method name getDerivedStateFromProps comprises five different words, “Get Derived State From Props”.

方法名称getDerivedStateFromProps包含五个不同的词,“从道具获取派生状态”。

Also, component state in this manner is referred to as Derived State.

而且,将以此方式的组件状态称为派生状态。

As a rule of thumb, derived state should be used sparingly as you can introduce subtle bugs into your application if you aren’t sure of what you’re doing.

根据经验,应谨慎使用派生状态,因为如果您不确定自己在做什么,就可以在应用程序中引入一些细微的错误

getSnapshotBeforeUpdate。 (getSnapshotBeforeUpdate.)

In the updating component phase, right after the render method is called, the getSnapshotBeforeUpdate lifecycle method is called next.

在更新组件阶段, getSnapshotBeforeUpdate在调用render方法之后,接下来将调用getSnapshotBeforeUpdate生命周期方法。

This one is a little tricky, but I’ll take my time to explain how it works.

这个有点棘手,但是我将花一些时间来解释它是如何工作的。

Chances are you may not always reach out for this lifecycle method, but it may come in handy in certain special cases. Specifically when you need to grab some information from the DOM (and potentially change it) just after an update is made.

您可能不总是可以使用这种生命周期方法,但是在某些特殊情况下,它可能会派上用场。 具体来说,当您需要在更新后立即从DOM中获取一些信息(并可能对其进行更改)时。

Here’s the important thing. The value queried from the DOM in getSnapshotBeforeUpdate will refer to the value just before the DOM is updated. Even though the render method was previously called.

这是重要的事情。 从getSnapshotBeforeUpdate的DOM查询的值将引用DOM更新之前的值。 即使先前已调用render方法。

An analogy that may help has to do with how you use version control systems such as git.

可能与您使用版本控制系统(如git)有关的类推可能与之类似。

A basic example is that you write code, and stage your changes before pushing to the repo.

一个基本的示例是您编写代码,并在进行回购之前先进行更改。

In this case, assume the render function was called to stage your changes before actually pushing to the DOM. So, before the actual DOM update, information retrieved from getSnapshotBeforeUpdate refers to those before the actual visual DOM update.

在这种情况下,假设在实际推送到DOM之前调用了render函数来暂存更改。 因此,在实际DOM更新之前,从getSnapshotBeforeUpdate检索的信息是指实际可视DOM更新之前的信息。

Actual updates to the DOM may be asynchronous, but the getSnapshotBeforeUpdate lifecycle method will always be called immediately before the DOM is updated.

DOM的实际更新可能是异步的,但是始终在DOM更新之前立即调用getSnapshotBeforeUpdate生命周期方法。

Don’t worry if you don’t get it yet. I have an example for you.

如果您还没有得到,请不要担心。 我有个例子给你。

The implementation of the chat pane is as simple as you may have imagined. Within the App component is an unordered list with a Chats component:

聊天窗格的实现就像您想象的一样简单。 在App组件内是带有Chats组件的无序列表:

<ul className="chat-thread">    <Chats chatList={this.state.chatList} /> </ul>

The Chats component renders the list of chats, and for this, it needs a chatList prop. This is basically an Array. In this case, an array of 3 string values, "Hey", "Hello", "Hi".

Chats组件呈现了聊天列表,为此,它需要一个chatList道具。 这基本上是一个数组。 在这种情况下,将包含3个字符串值的数组“ Hey”,“ Hello”,“ Hi”。

The Chats component has a simple implementation as follows:

Chats组件具有以下简单实现:

class Chats extends Component {  render() {    return (      <React.Fragment>        {this.props.chatList.map((chat, i) => (          <li key={i} className="chat-bubble">            {chat}          </li>        ))}      </React.Fragment>    );  }}

It just maps through the chatList prop and renders a list item which is in turn styled to look like a chat bubble :).

它只是通过chatList映射,并呈现一个列表项,该列表项的样式又类似于聊天气泡:)。

There’s one more thing though. Within the chat pane header is an “Add Chat” button.

不过还有一件事。 聊天窗格标题中有一个“添加聊天”按钮。

Clicking this button will add a new chat text, “Hello”, to the list of rendered messages.

单击此按钮将在呈现的消息列表中添加新的聊天文本“ Hello”。

Here’s that in action:

实际情况如下:

The problem here, as with most chat applications, is that whenever the number of chat messages exceeds the available height of the chat window, the expected behaviour is to auto scroll down the chat pane so that the latest chat message is visible. That’s not the case now.

与大多数聊天应用程序一样,这里的问题是,每当聊天消息的数量超过聊天窗口的可用高度时,预期的行为就是自动向下滚动聊天窗格,以便显示最新的聊天消息。 现在不是这种情况。

Let’s see how we may solve this using the getSnapshotBeforeUpdate lifecycle method.

让我们看看如何使用getSnapshotBeforeUpdate生命周期方法解决此问题。

The way the getSnapshotBeforeUpdate lifecycle method works is that when it is invoked, it gets passed the previous props and state as arguments.

getSnapshotBeforeUpdate生命周期方法的工作方式是,当调用它时,它将传递先前的props和state作为参数。

So we can use the prevProps and prevState parameters as shown below:

因此,我们可以使用prevPropsprevState参数,如下所示:

getSnapshotBeforeUpdate(prevProps, prevState) {   }

Within this method, you’re expected to either return a value or null:

在此方法中,期望您返回一个值或null:

getSnapshotBeforeUpdate(prevProps, prevState) {   return value || null // where 'value' is a  valid JavaScript value    }

Whatever value is returned here is then passed on to another lifecycle method. You’ll get to see what I mean soon.

此处返回的任何值都将传递给另一个生命周期方法。 您很快就会明白我的意思。

The getSnapshotBeforeUpdate lifecycle method doesn't work on its own. It is meant to be used in conjunction with the componentDidUpdate lifecycle method.

getSnapshotBeforeUpdate生命周期方法无法getSnapshotBeforeUpdate 。 它应与componentDidUpdate生命周期方法结合使用。

Whatever value is returned from the getSnapshotBeforeUpdate lifecycle method is passed as the third argument to the componentDidUpdate method.

getSnapshotBeforeUpdate生命周期方法返回的任何值都将作为第三个参数传递给componentDidUpdate方法。

Let’s call the returned value from getSnapshotBeforeUpdate, snapshot, and here's what we get thereafter:

让我们调用getSnapshotBeforeUpdatesnapshot的返回值,这是此后的结果:

componentDidUpdate(prevProps, prevState, snapshot) { }

The componentDidUpdate lifecycle method is invoked after the getSnapshotBeforeUpdate is invoked. As with the getSnapshotBeforeUpdate method it receives the previous props and state as arguments. It also receives the returned value from getSnapshotBeforeUpdate as final argument.

在调用getSnapshotBeforeUpdate之后,将调用componentDidUpdate生命周期方法。 与getSnapshotBeforeUpdate方法一样,它接收以前的道具和状态作为参数。 它还从getSnapshotBeforeUpdate接收返回的值作为最终参数。

Here’s all the code required to maintain the scroll position within the chat pane:

这是在聊天窗格中保持滚动位置所需的所有代码:

getSnapshotBeforeUpdate(prevProps, prevState) {    if (this.state.chatList > prevState.chatList) {      const chatThreadRef = this.chatThreadRef.current;      return chatThreadRef.scrollHeight - chatThreadRef.scrollTop;    }    return null;  }  componentDidUpdate(prevProps, prevState, snapshot) {    if (snapshot !== null) {      const chatThreadRef = this.chatThreadRef.current;      chatThreadRef.scrollTop = chatThreadRef.scrollHeight - snapshot;    }  }

Let me explain what’s going on there.

让我解释一下发生了什么。

Below’s the chat window:

下面是聊天窗口:

However, the graphic below highlights the actual region that holds the chat messages (the unordered list, ul which houses the messages).

但是,下面的图形突出显示了保存聊天消息的实际区域(无序列表, ul容纳消息)。

It is this ul we hold a reference to using a React Ref:

正是这种ul我们认为一个参考使用阵营参考:

<ul className="chat-thread" ref={this.chatThreadRef}>   ...</ul>

First off, because getSnapshotBeforeUpdate may be triggered for updates via any number of props or even a state update, we wrap to code in a conditional that checks if there’s indeed a new chat message:

首先,由于可以通过任意数量的道具甚至状态更新来触发getSnapshotBeforeUpdate进行更新,因此我们将代码包装成一个条件,以检查是否确实存在新的聊天消息:

getSnapshotBeforeUpdate(prevProps, prevState) {    if (this.state.chatList > prevState.chatList) {      // write logic here    }  }

The getSnapshotBeforeUpdate method above has to return a value yet. If no chat message was added, we will just return null:

上面的getSnapshotBeforeUpdate方法必须返回一个值。 如果没有添加聊天消息,我们将只返回null

getSnapshotBeforeUpdate(prevProps, prevState) {    if (this.state.chatList > prevState.chatList) {      // write logic here    }      return null }

Now consider the full code for the getSnapshotBeforeUpdate method:

现在考虑getSnapshotBeforeUpdate方法的完整代码:

getSnapshotBeforeUpdate(prevProps, prevState) {    if (this.state.chatList > prevState.chatList) {      const chatThreadRef = this.chatThreadRef.current;      return chatThreadRef.scrollHeight - chatThreadRef.scrollTop;    }    return null;  }

Does it make sense to you?

这对您有意义吗?

Not yet, I suppose.

我想还没有。

First, consider a situation where the entire height of all chat messages doesn’t exceed the height of the chat pane.

首先,考虑一种情况,其中所有聊天消息的总高度不超过聊天窗格的高度。

Here, the expression chatThreadRef.scrollHeight - chatThreadRef.scrollTop will be equivalent to chatThreadRef.scrollHeight - 0.

在这里,表达式chatThreadRef.scrollHeight - chatThreadRef.scrollTop等效于chatThreadRef.scrollHeight - 0

When this is evaluated, it’ll be equal to the scrollHeight of the chat pane — just before the new message is inserted to the DOM.

评估后,它等于聊天窗格的scrollHeight -就在将新消息插入DOM之前。

If you remember from the previous explanation, the value returned from the getSnapshotBeforeUpdate method is passed as the third argument to the componentDidUpdate method. We call this snapshot:

如果您还记得前面的解释,则将从getSnapshotBeforeUpdate方法返回的值作为第三个参数传递给componentDidUpdate method 。 我们将此快照称为:

componentDidUpdate(prevProps, prevState, snapshot) {}

The value passed in here — at this time, is the previous scrollHeight before the update to the DOM.

此处传入的值是当前更新到DOM之前的上一个scrollHeight

In the componentDidUpdate we have the following code, but what does it do?

componentDidUpdate我们有以下代码,但是它做什么?

componentDidUpdate(prevProps, prevState, snapshot) {    if (snapshot !== null) {      const chatThreadRef = this.chatThreadRef.current;      chatThreadRef.scrollTop = chatThreadRef.scrollHeight - snapshot;    }  }

In actuality, we are programmatically scrolling the pane vertically from the top down, by a distance equal to chatThreadRef.scrollHeight - snapshot;

实际上,我们以编程方式从上向下垂直滚动窗格,其距离等于chatThreadRef.scrollHeight - snapshot

Since snapshot refers to the scrollHeight before the update, the above expression returns the height of the new chat message plus any other related height owing to the update.

由于snapshot是指更新前的scrollHeight ,因此上面的表达式返回新聊天消息的height以及由于更新而导致的任何其他相关高度。

Please see the graphic below:

请参见下图:

When the entire chat pane height is occupied with messages (and already scrolled up a bit), the snapshot value returned by the getSnapshotBeforeUpdate method will be equal to the actual height of the chat pane.

当整个聊天窗格的高度都被消息占据(并且已经向上滚动)时,由getSnapshotBeforeUpdate方法返回的snapshot值将等于聊天窗格的实际高度。

The computation from componentDidUpdate will set to scrollTop value to the sum of the heights of extra messages - exactly what we want.

componentDidUpdate的计算将设置为scrollTop值,即额外消息的高度之和-正是我们想要的。

Yeah, that’s it.

是的,就是这样。

If you got stuck, I’m sure going through the explanation (one more time) or checking the source code will help clarify your questions.

如果您遇到困难,我相信可以再进行一次解释(检查一次)或检查源代码将有助于澄清您的问题。

错误处理生命周期方法。 (The Error Handling Lifecycle Methods.)

Sometimes things go bad, errors are thrown. The error lifecycle methods are invoked when an error is thrown by a descendant component i.e a component below them.

有时情况会变糟,会引发错误。 当后代组件(即它们下方的组件)引发错误时,将调用错误生命周期方法。

Let’s implement a simple component to catch errors in the demo app. For this, we’ll create a new component called ErrorBoundary.

让我们实现一个简单的组件来捕获演示应用程序中的错误。 为此,我们将创建一个名为ErrorBoundary的新组件。

Here’s the most basic implementation:

这是最基本的实现:

import React, { Component } from 'react';class ErrorBoundary extends Component {  state = {};  render() {    return null;  }}export default ErrorBoundary;

Now, let’s incorporate the error lifecycle methods.

现在,让我们结合错误生命周期方法。

静态getDerivedStateFromError。 (static getDerivedStateFromError.)

Whenever an error is thrown in a descendant component, this method is called first, and the error thrown passed as an argument.

每当在后代组件中引发错误时,将首先调用此方法,并将引发的错误作为参数传递。

Whatever value is returned from this method is used to update the state of the component.

从此方法返回的任何值都将用于更新组件的状态。

Let’s update the ErrorBoundary component to use this lifecycle method.

让我们更新ErrorBoundary组件以使用此生命周期方法。

import React, { Component } from "react";
class ErrorBoundary extends Component {  state = {};
static getDerivedStateFromError(error) {    console.log(`Error log from getDerivedStateFromError: ${error}`);    return { hasError: true };  }
render() {    return null;  }}
export default ErrorBoundary;

Right now, whenever an error is thrown in a descendant component, the error will be logged to the console, console.error(error), and an object is returned from the getDerivedStateFromError method. This will be used to update the state of the ErrorBoundary component i.e with hasError: true.

现在,每当在后代组件中引发错误时,该错误将记录到控制台console.error(error ),并从getDerivedStateFromError方法返回一个对象。 这将用于更新ErrorBoundary组件的状态,即具有hasError: true

componentDidCatch。 (componentDidCatch.)

The componentDidCatch method is also called after an error in a descendant component is thrown. Apart from the error thrown, it is passed one more argument which represents more information about the error:

在抛出后代组件中的错误之后,也会调用componentDidCatch方法。 除了引发的错误之外,还传递了另一个参数,该参数表示有关该错误的更多信息:

componentDidCatch(error, info) {}

In this method, you can send the error or info received to an external logging service. Unlike getDerivedStateFromError, the componentDidCatch allows for side-effects:

通过这种方法,您可以将收到的errorinfo发送到外部日志记录服务。 与getDerivedStateFromError不同, componentDidCatch具有副作用:

componentDidCatch(error, info) {	logToExternalService(error, info) // this is allowed.         //Where logToExternalService may make an API call.}

Let’s update the ErrorBoundary component to use this lifecycle method:

让我们更新ErrorBoundary组件以使用此生命周期方法:

import React, { Component } from "react";
class ErrorBoundary extends Component {  state = { hasError: false };  static getDerivedStateFromError(error) {    console.log(`Error log from getDerivedStateFromError: ${error}`);    return { hasError: true };  }  componentDidCatch(error, info) {    console.log(`Error log from componentDidCatch: ${error}`);    console.log(info);  }  render() {    return null  }}export default ErrorBoundary;

Also, since the ErrorBoundary can only catch errors from descendant components, we’ll have the component render whatever is passed as Children or render a default error UI if something went wrong:

此外,由于ErrorBoundary只能从后续组件发现错误,我们就会有分量渲染无论是作为传递Children或呈现一个默认的错误UI如果出事了:

... render() {    if (this.state.hasError) {      return <h1>Something went wrong.</h1>;    }
return this.props.children; }

I have simulated a javascript error whenever you add a 5th chat message.

每当您添加第5条聊天消息时,我都会模拟一个javascript错误。

Have a look at the error boundary at work:

看一下工作中的错误边界:

结论。 (Conclusion.)

It is worth mentioning that while new additions were made to the component lifecycle methods, some other methods such as componentWillMount , componentWillUpdate, componentWillReceiveProps were deprecated.

值得一提的是,虽然组件生命周期方法有了新的增加,但不建议使用其他一些方法,例如componentWillMountcomponentWillUpdatecomponentWillReceiveProps

Now you’re up to date on the changes made to the component lifecycle methods since React version 16!

现在您可以了解从React版本16开始对组件生命周期方法所做的更改!

第2章:使用Context API简化状态管理。 (Chapter 2: Simpler State Management with the Context API.)

John’s an amazing developer, and he loves what he does. However, he’s frequently been facing the same problem when writing React applications.

约翰是一位了不起的开发人员,他喜欢他的所作所为。 但是,他在编写React应用程序时经常遇到相同的问题。

Props drilling, the term used to describe passing down props needlessly through a deeply nested component tree, has plagued John for a while now!

道具钻Kong ,这个术语用来描述不必要地通过深嵌套的组件树向下传递道具,这已经困扰了约翰一阵子!

Luckily, John has a friend who always has all the answers. Hopefully, she can suggest a way out.

幸运的是,约翰有一个朋友,他总是能得到所有答案。 希望她可以提出一条出路。

John reaches out to Mia, and she steps in to offer some advice.

John向Mia伸出手,她介入并提供了一些建议。

Mia is a fabulous engineer as well, and she suggests using some state management library such as Redux or MobX.

Mia也是一位出色的工程师,她建议使用某些状态管理库,例如ReduxMobX

These are great choices, however, for most of John’s use cases, he finds them a little too bloated for his need.

这些都是不错的选择,但是,对于John的大多数用例而言,他发现它们对于他的需求而言过于too肿。

Can’t I have something simpler and native to React itself”, says John.

我不能为React本身提供更简单,更原生的东西吗 ”,约翰说。

Mia goes on a desperate search to help a friend in need, and she finds the Context API.

Mia进行了一次绝望的搜索,以帮助有需要的朋友,然后她找到了Context API。

Mia recommends using React’s Context API to solve the problem. John is now happy, excited to see what the Context API could offer, and he goes about his work productively.

Mia建议使用React的Context API解决问题。 John现在很高兴,很高兴看到Context API可以提供什么,并且他的工作富有成效。

This marks the beginning of John’s experience with the Context API.

这标志着John体验Context API的开始。

上下文介绍。 (Introduction to Context.)

The Context API exists to make it easy to share data considered “global” within a component tree.

存在Context API是为了使在组件树中共享“全局”数据变得容易。

Let’s have a look at an illustrated example before we delve into writing any code.

在深入研究编写任何代码之前,让我们看一个示例示例。

Well, John has began working with the Context API and has mostly been impressed with it so far. Today he has a new project to work on, and he intends to use the context API.

好吧,John已经开始使用Context API,到目前为止,印象最深的是。 今天,他有一个新项目要进行,他打算使用上下文API。

Let’s see what this new project is about.

让我们看看这个新项目是关于什么的。

John is expected to build a game for a new client of his. This game is called Benny Home Run, and it seems like a great place to use the Context API.

预计约翰将为他的新客户制作游戏。 该游戏称为Benny Home Run ,它似乎是使用Context API的好地方。

The sole aim of the game is to move Benny from his start position to his new home.

游戏的唯一目的是将Benny从开始的位置转移到他的新家。

To build this game, John must keep track of the position of Benny.

要制作此游戏,John必须跟踪Benny的位置。

Since Benny’s position is such an integral part of the entire application, it may as well be tracked via some global application state.

由于Benny的职位是整个应用程序不可或缺的一部分,因此还可以通过某些全局应用程序状态来跟踪它。

Did I just say “global application state”?

我只是说“ 全局应用程序状态 ”吗?

Yeah!

是的

That sounds like a good fit for the Context API.

听起来很适合Context API。

So, how may John build this?

那么,约翰怎么可能建造这个呢?

First, there’s the need to import the createContext method from React

首先,需要从React导入createContext方法

import {createContext} from 'react';

The createContext method allows you to create what’s referred to as a context object. Consider this to be the data structure that powers retrieving and saving state values.

createContext方法允许您创建所谓的上下文对象。 将其视为支持检索和保存状态值的数据结构。

To create a context object, you invoke the createContext method with (or without) an initial state value to be saved in the context object.

要创建上下文对象,请使用带有(或不带有)要保存在上下文对象中的初始状态值来调用createContext方法。

createContext(initialStateValue)

Here’s what that looks like in the Benny app:

这是Benny应用程序中的样子:

const BennyPositionContext = createContext({ 	x: 50, 	y: 50 })

The createContext method is invoked with an initial state value corresponding to the initial position values (x and y) for Benny.

用与Benny的初始位置值(x和y)相对应的初始状态值调用createContext方法。

Looks good!

看起来挺好的!

But, after creating a context object, how exactly do you gain access to the state values within your application?

但是,在创建上下文对象之后,您如何才能完全访问应用程序中的状态值?

Well, every context object comes with a Provider and Consumer component.

好吧,每个上下文对象都带有ProviderConsumer组件。

The Provider component “provides” the value saved in the context object to its children, while the Consumer component “consumes” the values from within any child component.

Provider组件将上下文对象中保存的值“提供”给它的子组件,而Consumer组件从任何子组件中“消耗”值。

I know that was a mouth full, so let’s break it apart slowly.

我知道那是满嘴的,所以让我们慢慢地把它分开。

In the Benny example, we can go ahead and destructure the BennyPositionContext to retrieve the Provider and Consumer components.

在Benny示例中,我们可以继续对BennyPositionContext进行BennyPositionContext以检索Provider和Consumer组件。

const BennyPositionContext = createContext({ 	x: 50, 	y: 50 })// get provider and consumerconst { Provider, Consumer } = BennyPositionContext

Since Provider provides value saved in the context object to its children, we could wrap a tree of components with the Provider component as shown below:

由于Provider将保存在上下文对象中的值提供给它的对象,因此我们可以使用Provider组件包装一棵组件树,如下所示:

&lt;Provider>   <Root /> // the root component for the Benny app. </Provider>

Now, any child component within the Root component will have access to the default values stored in the context object.

现在, Root组件中的任何子组件都可以访问存储在上下文对象中的默认值。

Consider the following tree of components for the Benny app.

考虑以下Benny应用程序的组件树。

<Provider>   <Scene>     <Benny />   &lt;/Scene></Provider>

Where Scene and Benny are children of the Root component and represent the game scene and benny character respectively.

其中SceneBennyRoot组件的子代,分别代表游戏场景和benny角色。

In this example, the Scene or the even more nested Benny component will have access to the value provided by the Provider component.

在此示例中, Scene或更嵌套的Benny组件将有权访问Provider组件提供的值。

It is worth mentioning that a Provider also takes in a value props.

值得一提的是, Provider还接受了value道具。

This value prop is useful if you want to provide a value other than the initial value passed in at the context object creation time via createContext(initialStateValue)

这个value如果要提供比通过在上下文中创建对象的时候传递的初始值以外的值的道具是非常有用createContext(initialStateValue)

Here’s an example where a new set of values are passed in to the Provider component.

这是一个将一组新值传递到Provider组件的示例。

<Provider value={x: 100, y: 150}>   <Scene>     <Benny />   &lt;/Scene></Provider>

Now that we have values provided by the Provider component, how can a nested component such as Benny consume this value?

现在我们有了Provider组件提供的值,嵌套的组件(例如Benny使用此值?

The simple answer is by using the Consumer component.

简单的答案是使用Consumer组件。

Consider the Benny component being a simple component that renders some SVG.

认为Benny组件是呈现某些SVG的简单组件。

const Benny = () => {  return <svg />}

Now, within Benny we can go ahead and use the Consumer component like this:

现在,在Benny我们可以继续使用Consumer组件,如下所示:

const Benny = () => {  return <Consumer>  {(position) => <svg /&gt;}</Consumer>}

Okay, what’s going on there?

好吧,那是怎么回事?

The Consumer component exposes a render prop API i.e children is a function. This function is then passed arguments corresponding to the values saved in the context object. In this case, the position object with the x and y coordinate values.

Consumer组件公开了一个渲染道具API,即children是一个函数。 然后将与上下文对象中保存的值相对应的参数传递给该函数。 在这种情况下,具有xy坐标值的position对象。

It is worth noting that whenever the value from a Provider component changes, the associated Consumer component and the children will be re-rendered to keep the value(s) consumed in sync.

值得注意的是,只要Provider组件的值发生更改,相关的Consumer组件和子组件都会重新呈现,以使消耗的值保持同步。

Also, a Consumer will receive values from the closest Provider above it in the tree.

同样, Consumer将从树中它上面最接近的Provider那里接收值。

Consider the situation below:

考虑以下情况:

// create context object const BennyPositionContext = createContext({ 	x: 50, 	y: 50 })
// get provider and consumerconst { Provider, Consumer } = BennyPositionContext
// wrap Root component in a Provider&lt;Provider>  <Root />;</Provider>
// in Benny, within Root. const Benny = () => (  <Provider value={x: 100, y: 100}>    // do whatever  </Provider>)

Now, with a new provider component introduced in Benny, any Consumer within Benny will receive the value {x: 100, y: 100} NOT the initial value of {x: 50, y: 50}.

现在,在引入了新的提供者组件Benny ,任何ConsumerBenny将获得的值{x: 100, y: 100} NOT的初始值{x: 50, y: 50}

This is a contrived illustrated example, but it helps solidify the foundations of using the Context API.

这是一个人为的示例,但是它有助于巩固使用Context API的基础。

Having understood the necessary building blocks for using the Context API, let’s build an application utilizing all you’ve learned so far, with extra use cases discussed.

在了解了使用Context API的必要构造块之后,让我们利用到目前为止所学的内容来构建应用程序,并讨论其他用例。

示例:迷你银行应用程序。 (Example: The Mini-Bank Application.)

John’s an all round focused guy. When he’s not working on projects from his work place, he dabbles into side projects.

约翰是个全能的人。 当他不在工作场所从事项目时,他会涉足其他项目。

One of his many side projects is a bank application he thinks could shape the future of banking. How so.

他认为许多辅助项目之一是银行应用程序,他认为这可能会影响银行业的未来。 为何如此。

I managed to get the source code for this application. You’ll find it in the code repository for the book as well.

我设法获得了此应用程序的源代码。 您也可以在本书的代码存储库中找到它。

To get started, please Install the dependencies and run the application by following the commands below:

首先,请按照以下命令安装依赖项并运行应用程序:

cd 02-Context-API/bank-appyarn installyarn start

Once that’s done, the application starts off with a login screen as seen below:

完成后,该应用程序将以登录屏幕启动,如下所示:

You can enter whatever username and password combination of your choosing.

您可以输入您选择的任何用户名和密码组合。

Upon login in you’ll be presented with the application’s main screen shown below:

登录后,将显示以下应用程序主屏幕:

In the main screen you can perform actions such as viewing your bank account balance and making withdrawals as well.

在主屏幕中,您可以执行一些操作,例如查看银行帐户余额和提款。

Our goal here is to manage the state in this application a lot better by introducing React’s Context.

我们的目标是通过引入React的Context更好地管理此应用程序中的状态。

识别正在钻探的道具。 (Identifying Props being Drilled.)

The root component of the application is called Root, and has the implementation below:

该应用程序的根组件称为Root ,其实现如下:

... import { USER } from './api'class Root extends Component {  state = {    loggedInUser: null  }  handleLogin = evt => {    evt.preventDefault()    this.setState({      loggedInUser: USER    })  }  render () {    const { loggedInUser } = this.state    return loggedInUser ? (      &lt;App loggedInUser={loggedInUser} />    ) : (      <Login handleLogin={this.handleLogin} /&gt;    )  }}

If the user is logged in, the main component App is rendered, else we show the Login component.

如果用户已登录,则将呈现主要组件App ,否则我们将显示Login组件。

... loggedInUser ? (      &lt;App loggedInUser={loggedInUser} />    ) : (   <Login handleLogin={this.handleLogin} />)...

Upon a successful login (which doesn’t require any particular username and password combinations), the state of the Root application is updated with a loggedInUser.

成功登录后(不需要任何特定的用户名和密码组合),将使用loggedInUser更新Root应用程序的state

...handleLogin = evt => {    ...    this.setState({      loggedInUser: USER    })  }...

In the real world, this could come from an api call.

在现实世界中,这可能来自api调用。

For this application, I’ve created a fake user in the api directory that exports the following user object.

对于此应用程序,我在api目录中创建了一个虚假用户,该用户导出以下用户对象。

export const USER = {  name: 'June',  totalAmount: 2500701}

Basically, the name and totalAmount of the user’s bank account are retrieved and set to state when you log in.

基本上,将检索用户银行帐户的nametotalAmount并将其设置为登录时的状态。

How’s the user object used in the application?

用户对象在应用程序中如何使用?

Well, consider the main component, App. This is the component responsible for rendering everything in the app other than the Login screen.

好吧,请考虑主要组件App 。 这是负责呈现应用程序中除“ Login屏幕以外的所有内容的组件。

Here’s the implementation of that:

这是它的实现:

class App extends Component {  state = {    showBalance: false  }  displayBalance = () => {    this.setState({ showBalance: true })  }  render () {    const { loggedInUser } = this.props    const { showBalance } = this.state    return (      <div className='App'>            <User loggedInUser={loggedInUser} profilePic={photographer} />	<ViewAccountBalance          showBalance={showBalance}          loggedInUser={loggedInUser}          displayBalance={this.displayBalance}        />        <section>;          <WithdrawButton amount={10000} />          <WithdrawButton amount={5000} /&gt;        </section>        <Charity />      </div>    )  }}

It’s a lot simpler that it seems. Have a second look!

看起来要简单得多。 再看看!

The loggedInUser is passed as props to App from Root, and is also passed down to both User and ViewAccountBalance components.

loggedInUser作为道具从Root传递给App ,也向下传递给UserViewAccountBalance组件。

The User component receives the loggedInUser prop and passes it down to another child component, Greeting which renders the text, “Welcome, June”.

User组件接收loggedInUser并将其向下传递到另一个子组件Greeting ,该文本呈现文本“ Welcome,June ”。

//User.js const User = ({ loggedInUser, profilePic }) => {  return (    <div>      <img  src={profilePic} alt='user' />      <Greeting loggedInUser={loggedInUser} />    </div>  )}

Also, ViewAccountBalance takes in a boolean prop showBalance which decides whether to show the account balance or not. This is toggled to true when you click the “yes” button.

此外, ViewAccountBalance接受布尔showBalance ,该showBalance决定是否显示帐户余额。 当您单击“是”按钮时,此选项切换为true

//ViewAccountBalance.jsconst ViewAccountBalance = ({ showBalance, loggedInUser, displayBalance }) => {  return (    <Fragment>      {!showBalance ? (        <div>          <p>            Would you love to view your account balance?          </p>          <button onClick={displayBalance}>            Yes          </button>        </div>      ) : (        <TotalAmount totalAmount={loggedInUser.totalAmount} />      )}    </Fragment>  )}

From the code block above, do you also see that ViewAccountBalance receives the loggedInUser prop only to pass it to TotalAmount?

从上面的代码块,你也看到, ViewAccountBalance接收loggedInUser唯一道具将它传递给TotalAmount

TotalAmount receives this prop, retrieves the totalAmount from the user object and renders the total amount.

TotalAmount接收此道具,从用户对象中检索totalAmount并呈现总数。

I’m pretty sure you can figure out whatever else is going on in the code snippets above.

我很确定您可以弄清楚上面代码片段中发生的所有其他情况。

Having explained the code so far, do you see the obvious props drilling here?

到目前为止,已经解释了代码,您是否在这里看到了明显的道具?

loggedInUser is passed down way too many times to components that don’t even need to know about it.

loggedInUser太多次传递给甚至不需要了解它的组件。

Let’s fix that with the Context API.

让我们使用Context API修复它。

避免使用上下文进行道具钻探。 (Avoid Props Drilling with Context.)

One easy solution is to look at the Root component where we began passing props down and finding a way to introduce a context object in there.

一个简单的解决方案是查看Root组件,在该组件中我们开始传递props,并找到一种在其中引入上下文对象的方法。

Going by that solution, we could create a context object with no initial default values above the Root class declaration:

通过该解决方案,我们可以创建一个上下文对象,该对象在Root类声明上方没有初始默认值:

const { Provider, Consumer } = createContext()class Root extends Component {  state = {    loggedInUser: null  }  ... }

Then we could wrap the main App component around the Provider with a value prop.

然后,我们可以使用值属性将主要App组件包装在Provider周围。

class Root extends Component {  state = {    loggedInUser: null  }  ...   render () {    ...    return loggedInUser ? (      <Provider value={this.state.loggedInUser}>        <App loggedInUser={loggedInUser} />      </Provider>    ) ...

Initially, the Provider value prop will be null, but as soon as a user logs in and the state is updated in Root, the Provider will also receive the current loggedInUser.

最初, Provider value prop将为null ,但是一旦用户登录并且Rootstate被更新, Provider还将接收当前的loggedInUser

With this done we can import the Consumer wherever we want and do away with passing props needlessly down the component tree.

完成此操作后,我们可以将Consumer导入到我们想要的任何位置,并且不需要在组件树中不必要地传递道具。

For example here’s the Consumer used in the Greeting component:

例如,这是在Greeting组件中使用的Consumer

import { Consumer } from '../Root'const Greeting = () => {  return (    <Consumer>      {user => <p>Welcome, {user.name}! </p>;}    </Consumer>  )}

We could go ahead and do the same everywhere else we’ve passed the loggedInUser prop needlessly.

我们可以继续进行,并在所有我们不必要地传递了loggedInUser道具的地方进行相同的操作。

And the app works just as before, only we got rid of passing down loggedInUser over and over again .

而且该应用程序的工作方式与以前一样,只是我们摆脱了一次又一次地传递loggedInUser

隔离实施细节。 (Isolating Implementation Details.)

The solution highlighted above works but not without some caveat.

上面突出显示的解决方案有效,但并非没有警告。

A better solution will be to centralise the logic for the user state and Provider in one place.

更好的解决方案是将用户状态和提供者的逻辑集中在一个地方。

This is pretty common practice. Let me show you what I mean.

这是很常见的做法。 让我告诉你我的意思。

Instead of having the Root component manage the state for loggedInUser, we will create a new file called UserContext.js.

我们将创建一个名为UserContext.js的新文件,而不是让Root组件管理loggedInUser的状态。

This file will have the related logic for updating loggedInUser as well as expose a context Provider and Consumer to make sure loggedInUser and any updater functions are accessible from anywhere in the component tree.

该文件将具有用于更新loggedInUser的相关逻辑,并公开上下文ProviderConsumer ,以确保可从组件树中的任何位置访问loggedInUser和任何更新程序功能。

This sort of modularity becomes important when you have many different context objects. For example, you could have a ThemeContext and LanguageContext object in the same app.

当您有许多不同的上下文对象时,这种模块化就变得很重要。 例如,您可能在同一应用程序中有一个ThemeContextLanguageContext对象。

Extracting these into separate files and components proves more manageable and effective over time.

随着时间的推移,将它们提取到单独的文件和组件中被证明更加可管理和有效。

Consider the following:

考虑以下:

// UserContext.jsimport React, { createContext, Component } from 'react'import { USER } from '../api'const { Provider, Consumer } = createContext()class UserProvider extends Component {  state = {    loggedInUser: null  }  handleLogin = evt => {    evt.preventDefault()    this.setState({      loggedInUser: USER    })  }  render () {    const { loggedInUser } = this.state    return (      <Provider        value={{          user: loggedInUser,          handleLogin: this.handleLogin        }}      >        {this.props.children}      </Provider>    )  }}export { UserProvider as default, Consumer as UserConsumer }

This represents the content of the new context/UserContext.js file. The logic previously handled by the Root component has been moved here.

这表示新的context/UserContext.js文件的内容。 Root组件先前处理的逻辑已移至此处。

Note how it handles every logic regarding the loggedInUser state value, and passes the needed values to children via a Provider.

请注意,它如何处理与loggedInUser状态值有关的所有逻辑,并通过Provider将所需的值传递给children

...<Provider     value={{       user: loggedInUser,       handleLogin: this.handleLogin      }}     >      {this.props.children}</Provider>...

In this case, the value prop is an object with the user value, and function to update it, handleLogin.

在这种情况下, value prop是具有user值的对象,并具有更新它的功能handleLogin

Also note that the Provider and Consumer are both exported. This makes it easy to consume the values from any components in the application.

另请注意,提供者和使用者都已导出。 这使得使用应用程序中任何组件的值变得容易。

export { UserProvider as default, Consumer as UserConsumer }

With this decoupled setup, you can use the loggedInUser state value anywhere in your component tree, and have it updated from anywhere in your component tree as well.

通过这种分离的设置,您可以在组件树中的任何位置使用loggedInUser状态值,也可以从组件树中的任何位置更新它。

Here’s an example of using this in the Greeting component:

这是在Greeting组件中使用此示例:

import React from 'react'import { UserConsumer } from '../context/UserContext'const Greeting = () => {  return (    <UserConsumer>      {({ user }) => <p>Welcome, {user.name}! </p>}    </UserConsumer>  )}export default Greeting

How easy.

多么简单

Now, I’ve taken the effort to delete every reference to loggedInUser where the prop had to be passed down needlessly. Thanks, Context!

现在,我已经努力删除了不必要地传递了对loggedInUser所有引用。 谢谢,上下文!

For example:

例如:

// before const User = ({ loggedInUser, profilePic }) => {  return (    <div>      <img src={profilePic} alt='user' />      <Greeting loggedInUser={loggedInUser} />    </div>  )}// after: Greeting consumes UserContext const User = ({profilePic }) => {  return (    <div>      <img src={profilePic} alt='user' />      <Greeting />     </div>  )}export default User

Be sure to look in the accompanying code folder for the final implementation i.e. after stripping off the loggedInUser from being passed down needlessly.

请确保在随附的代码文件夹中查找最终实现,即在loggedInUserloggedInUser不必要地传递之后。

更新上下文值。 (Updating Context Values.)

What’s a bank app if you can’t make withdrawals, huh?

如果您不能提款,那是什么银行应用程序,是吗?

Well, this app has some buttons. You click them and voilà, a withdrawal is made.

好吧,这个程序有一些按钮。 您单击它们,然后退出。

Since the totalAmount value resides in the loggedInUser object, we may as well have the logic to make withdrawals in the UserContext.js file.

由于totalAmount值位于loggedInUser对象,大家不妨不得不在提款逻辑UserContext.js文件。

Remember we’re trying to centralise all logic related to the user object in one place.

记住,我们试图将与用户对象相关的所有逻辑集中在一个地方。

To do this, we’ll extend the UserProvider in UserContext.js to include a handleWithdrawal method.

为此,我们将在UserContext.js扩展UserProvider ,以包括handleWithdrawal方法。

// UserContext.js... handleWithdrawal = evt => {    const { name, totalAmount } = this.state.loggedInUser    const withdrawalAmount = evt.target.dataset.amount

When you click any of the buttons, we will invoke this handleWithdrawal method.

当您单击任何按钮时,我们将调用此handleWithdrawal方法。

From the evt click object passed as an argument to the handleWithdrawal method, we then pull out the amount to be withdrawn from the dataset object.

然后从作为参数传递给handleWithdrawal方法的evt click对象中,从dataset对象中提取要提取的金额。

const withdrawalAmount = evt.target.dataset.amount

This is possible because both buttons have a data-amount attribute set on them. For example:

这是可能的,因为两个按钮上都设置了data-amount属性。 例如:

<button data-amount=1000 />

Now that we have the handleWithdrawal method written out, we can expose it via the values prop passed to Provider as shown below:

现在我们已经编写了handleWithdrawal方法,我们可以通过传递给Providervalues prop来公开它,如下所示:

<Provider    value={{       user: loggedInUser,       handleLogin: this.handleLogin       handleWithdrawal: this.handleWithdrawal     }}   >  {this.props.children}</Provider>

Now, we’re all set to consume the handleWithdrawal method from anywhere in the component tree.

现在,我们都准备从组件树中的任何位置使用handleWithdrawal方法。

In this case, our focus is on the WithdrawButton component. Go ahead and wrap that in a UserConsumer, deconstruct the handleWithdrawal method and make it the click handler for the buttons as shown below:

在这种情况下,我们的重点是WithdrawButton组件。 继续并将其包装在UserConsumer ,解构handleWithdrawal方法,并使其成为按钮的单击处理程序,如下所示:

const WithdrawButton = ({ amount }) => {  return (    <UserConsumer>      {({ handleWithdrawal }) => (        <button          data-amount={amount}          onClick={handleWithdrawal}        >          WITHDRAW {amount}        </button>      )}    </UserConsumer>  )}

And that works!

那行得通!

结论 (Conclusion)

This illustrates that you can pass not only state values, but also their corresponding updater functions in a context Provider. These will be available to be consumed anywhere in your component tree.

这说明您不仅可以在状态提供程序中传递状态值,还可以传递其相应的更新程序函数。 这些将可以在组件树中的任何位置使用。

Having made the bank app work well with the Context API, I’m pretty sure John will be proud of the progress we’ve made!

通过Context API使bank应用程序运行良好,我很确定John将为我们取得的进步感到自豪!

第3章:ContextType —在没有使用者的情况下使用上下文。 (Chapter 3: ContextType — Using Context without a Consumer.)

So far, John has had a great experience with the Context. Thanks to Mia who recommended such great tool.

到目前为止,John在上下文方面具有丰富的经验。 感谢Mia推荐了如此出色的工具。

However, there’s a little problem.

但是,有一个小问题。

As John uses the context API more often, he begins to realise a problem.

随着John越来越多地使用上下文API,他开始意识到问题。

When you have multiple Consumers within a component, it results in having a lot of nested, not-so-pleasant code.

当一个组件中有多个Consumers ,它将导致有很多嵌套的,不太令人满意的代码。

Here’s an example.

这是一个例子。

While working on the Benny Home Run application, John had to create a new context object to hold the game level state of the current user.

在使用Benny Home Run应用程序时,John必须创建一个新的上下文对象来保存当前用户的游戏级别状态。

// initial context objectconst BennyPositionContext = createContext({ 	x: 50, 	y: 50 })
// another context object for game level i.e Level 0 - 5 const GameLevelContext = createContext(0)

Remember, it’s common practice to split related data into different context objects for reusability and performance (owing to the fact the every consumer is re-rendered when values within a Provider change)

请记住,通常的做法是将相关数据拆分为不同的上下文对象,以实现可重用性和性能(由于当Provider值发生更改时,每个使用者都会被重新呈现)

With these context objects, John goes ahead to use both Consumer components within the Benny component as follows.

有了这些上下文对象,John继续按如下方式使用Benny组件中的两个Consumer组件。

//grab consumer for PositionContextconst { Consumer: PositionConsumer } = BennyPositionContext
// grab consumer for GameLevelContextconst { Consumer: GameLevelConsumer } = GameLevelContext
// use both Consumers here.const Benny = () => {  return <PositionConsumer>    {position => <GameLevelConsumer>        {gameLevel => <svg /&gt;}    </GameLevelConsumer>}  </PositionConsumer>}

Do you notice that consuming values from both context objects results in very nested code?

您是否注意到使用两个上下文对象中的值会导致嵌套非常多的代码?

Well, this is one of the more common problem with consuming data with the Consumer component. With multiple consumer components, you begin to have a lot of nesting.

Well, this is one of the more common problem with consuming data with the Consumer component. With multiple consumer components, you begin to have a lot of nesting.

So, what can we do about this?

So, what can we do about this?

Firstly, when we learn about Hooks in a later chapter, you’ll come to see an almost perfect solution to this. In the mean time, let’s consider the solution available to Class components via something calledcontextType.

Firstly, when we learn about Hooks in a later chapter, you'll come to see an almost perfect solution to this. In the mean time, let's consider the solution available to Class components via something called contextType .

Using a Class Component with contextType. (Using a Class Component with contextType.)

To take advantage of contextType you’re required to work with a class component.

To take advantage of contextType you're required to work with a class component.

Consider the Benny component rewritten as a class component.

Consider the Benny component rewritten as a class component.

// create context objectconst { Provider, Consumer } = createContext({ x: 50, y: 50 })// Class componentclass Benny extends Component {  render () {    return <Consumer>    {position => <svg />}  &lt;/Consumer>  }}

In this example, Benny consumes the initial context values { x: 50, y: 50 } from the context object.

In this example, Benny consumes the initial context values { x: 50, y: 50 } from the context object.

However, using a Consumer forces you to use a render prop API that may lead to nested code.

However, using a Consumer forces you to use a render prop API that may lead to nested code.

Let’s get rid of the Consumer component by using the contextType class property.

Let's get rid of the Consumer component by using the contextType class property.

Getting this to work is fairly easy.

Getting this to work is fairly easy.

First, you set the contextType property on the class component to a context object.

First, you set the contextType property on the class component to a context object.

const BennyPositionContext = createContext({ x: 50, y: 50 })// Class Benny extends Component ... // look here ?Benny.contextType = BennyPositionContext

After setting the contextType property, you can go ahead to consume values from the context object by using this.context.

After setting the contextType property, you can go ahead to consume values from the context object by using this.context .

For example, to retrieve the position values { x: 50, y: 50 }:

For example, to retrieve the position values { x: 50, y: 50 } :

class Benny extends Component {  render () {    // look here. No nesting!    const position = this.context    return <svg />  }}
The Perfect Solution? (The Perfect Solution?)

Using the contextType class property is great, but not particularly the best solution in the world. You can only use one contextType within a class component. This means if you need to introduce multiple Consumers you’ll still have some nested code.

Using the contextType class property is great, but not particularly the best solution in the world. You can only use one contextType within a class component. This means if you need to introduce multiple Consumers you'll still have some nested code.

结论 (Conclusion)

The contextType property is does solve the nesting problem a little bit.

The contextType property is does solve the nesting problem a little bit.

However, when we discuss Hooks in a later chapter, you’ll see how much better the solution hooks offer is.

However, when we discuss Hooks in a later chapter, you'll see how much better the solution hooks offer is.

Chapter 4: React.memo === Functional PureComponent. (Chapter 4: React.memo === Functional PureComponent.)

A few weeks ago John refactored the Benny component to a PureComponent.

A few weeks ago John refactored the Benny component to a PureComponent .

Here’s what his change looked like:

Here's what his change looked like:

Well, that looks good.

Well, that looks good.

However, the only reason he refactored the Benny component to a class component was because be needed to extend React.PureComponent.

However, the only reason he refactored the Benny component to a class component was because be needed to extend React.PureComponent .

The solution works, but what if we could achieve the same effect without having to refactor from functional to class components?

The solution works, but what if we could achieve the same effect without having to refactor from functional to class components?

Refactoring large components just because you need a specific React feature isn’t the most pleasant of experiences.

Refactoring large components just because you need a specific React feature isn't the most pleasant of experiences.

How React.memo works. (How React.memo works.)

React.memo is the perfect replacement for the class’ PureComponent. All you do is wrap your functional component in the React.memo function call and you get the exact behaviour PureComponent gives.

React.memo is the perfect replacement for the class' PureComponent . All you do is wrap your functional component in the React.memo function call and you get the exact behaviour PureComponent gives.

Here’s a quick example:

Here's a quick example:

// before import React from 'react'function MyComponent ({name}) {     return ( <div>        Hello {name}.            </div>    )}export default MyComponent
// after import React, { memo } from 'react'export default  memo(function MyComponent ({name}) {    return ( <div>        Hello {name}.            </div&gt;    )})

So simple, it couldn’t get any easier.

So simple, it couldn't get any easier.

Technically, React.memo is a higher order function that takes in a functional component and returns a new memoized component.

Technically, React.memo is a higher order function that takes in a functional component and returns a new memoized component.

So, if props do not change, react will skip rendering the component and just use the previously memoized value.

So, if props do not change, react will skip rendering the component and just use the previously memoized value.

With this new found information, John refactors the functional component, Benny to use React.memo.

With this new found information, John refactors the functional component, Benny to use React.memo .

Handling Deeply Nested Props. (Handling Deeply Nested Props.)

React.memo does a props shallow comparison. By implication, if you have nested props objects, the comparison will fail.

React.memo does a props shallow comparison . By implication, if you have nested props objects, the comparison will fail.

To handle such cases, React.memo takes in a second argument, an equality check function.

To handle such cases, React.memo takes in a second argument, an equality check function.

Here’s a basic example:

这是一个基本示例:

import React, { memo } from 'react'export default  memo (function MyComponent (props) {      return ( <div>        Hello World from {props.name.surname.short}            </div&gt;    )}, equalityCheck)
function equalityCheck(prevProps, nextProps) {  // return perform equality check & return true || false}

If the equalityCheck function returns true, no re-render will happen. This would mean that the current props and previous props are the same. If you return false, then a re-render will occur.

If the equalityCheck function returns true , no re-render will happen. This would mean that the current props and previous props are the same. If you return false , then a re-render will occur.

If you’re concerned about incurring extra performance hits from doing a deep comparison, you may use the lodash isEqual utility method.

If you're concerned about incurring extra performance hits from doing a deep comparison, you may use the lodash isEqual utility method.

import { isEqual } from 'lodash'function equalityCheck(prevProps, nextProps) {	return isEqual(prevProps, nextProps) }
结论。 (Conclusion.)

React.memo brings the class PureComponent behaviour to functional components. We’ve also had a look at using the equalityCheck function as well. If you use the equalityCheck function, be sure to include checks for function props where applicable. Not doing so is a common source of bugs in many React applications.

React.memo brings the class PureComponent behaviour to functional components. We've also had a look at using the equalityCheck function as well. If you use the equalityCheck function, be sure to include checks for function props where applicable. Not doing so is a common source of bugs in many React applications.

Chapter 5: The Profiler — Identifying Performance Bottlenecks. (Chapter 5: The Profiler — Identifying Performance Bottlenecks.)

It’s Friday and Mia is headed back home. On her way home she can’t help but think to herself.

It's Friday and Mia is headed back home. On her way home she can't help but think to herself.

What have I achieved today?” Mia says her to herself.

What have I achieved today? ” Mia says her to herself.

After a long careful thought, she comes to the conclusion that she accomplished just one thing the entire day.

After a long careful thought, she comes to the conclusion that she accomplished just one thing the entire day.

Well, how is it possible that Mia only achieved one thing the entire day?

Well, how is it possible that Mia only achieved one thing the entire day?

That ‘one thing’ had better be a worthy feat!

That 'one thing' had better be a worthy feat!

So, what was Mia’s accomplishment for the day?

So, what was Mia's accomplishment for the day?

It turns out that all Mia accomplished today was sitting helplessly as her browser attempted to load a page for 7 hours!

It turns out that all Mia accomplished today was sitting helplessly as her browser attempted to load a page for 7 hours!

What???

什么???

Measuring Performance in React Apps. (Measuring Performance in React Apps.)

Web performance is a big deal. Nobody has all the time in the world to sit through minutes waiting for your webpage to load.

Web performance is a big deal. Nobody has all the time in the world to sit through minutes waiting for your webpage to load.

In order to measure performance and identify performance bottlenecks in your apps, it’s crucial to have some way to inspect how long it took your app’s components to render, and why they were rendered.

In order to measure performance and identify performance bottlenecks in your apps, it's crucial to have some way to inspect how long it took your app's components to render, and why they were rendered.

This is exactly why the Profiler exists.

This is exactly why the Profiler exists.

If you’ve been writing react for sometime, you might remember the react-addons-perf module.

If you've been writing react for sometime, you might remember the react-addons-perf module.

Well, that has been deprecated in favour of the Profiler.

Well, that has been deprecated in favour of the Profiler.

With the Profiler, you can:

With the Profiler, you can:

  • Collect timing information about each component

    Collect timing information about each component
  • Easily identify performance bottlenecks

    Easily identify performance bottlenecks
  • Be sure to have a tool compatible with concurrent rendering

    Be sure to have a tool compatible with concurrent rendering
Getting Started. (Getting Started.)

To keep this as pragmatic as possible, I have set up a tiny application we’re going to profile together. i.e measure performance. We’ll do this with the aid of the Profiler.

To keep this as pragmatic as possible, I have set up a tiny application we're going to profile together. ie measure performance. We'll do this with the aid of the Profiler.

I call the application fake-medium, and it looks like this:

I call the application fake-medium , and it looks like this:

You’ll find the source code for the application in the code repository for this book.

You'll find the source code for the application in the code repository for this book.

To Install the dependencies and run the app, run the following from the04-The-Profiler directory:

To Install the dependencies and run the app, run the following from the 04-The-Profiler directory:

cd fake-mediumyarn install yarn start

If you ran those commands, you should have the application running in your default browser, on port 3000 or similar.

If you ran those commands, you should have the application running in your default browser, on port 3000 or similar.

Finally, open your chrome devtools by pressing Command+Option+J (Mac) or Control+Shift+J (Windows, Linux, and Chrome OS).

Finally, open your chrome devtools by pressing Command+Option+J (Mac) or Control+Shift+J (Windows, Linux, and Chrome OS).

Then find the React chrome devtools extension tab and click it.

Then find the React chrome devtools extension tab and click it.

You’ll be presented with two tabs, elements and profiler.

You'll be presented with two tabs, elements and profiler.

You guessed right, our focus is on the profiler tab, so please click it.

You guessed right, our focus is on the profiler tab, so please click it.

Doing so will lead you to the following page:

Doing so will lead you to the following page:

How does the Profiler Work? (How does the Profiler Work?)

The Profiler works by recording a session of actual usage of your application. In this recording session it gathers information about the components in your application and displays some interesting information you can exploit to find performance bottlenecks.

The Profiler works by recording a session of actual usage of your application. In this recording session it gathers information about the components in your application and displays some interesting information you can exploit to find performance bottlenecks.

To get started, click the record button.

To get started, click the record button.

After clicking ‘record’, you then go ahead to use your application as you’d expect a user to.

After clicking 'record', you then go ahead to use your application as you'd expect a user to.

In this case, I’ve gone ahead to click the medium clap button 3 times!

In this case, I've gone ahead to click the medium clap button 3 times!

Once you’re done interacting with your application, hit stop to view the information the Profiler provides.

Once you're done interacting with your application, hit stop to view the information the Profiler provides.

Making Sense of the Profiler Results. (Making Sense of the Profiler Results.)

On the far right of the profiler screen, you’ll find a visual representation of the number of commits made during your interaction with your application.

On the far right of the profiler screen, you'll find a visual representation of the number of commits made during your interaction with your application.

Conceptually, react does work in 2 phases. The render phase where components are rendered and the virtual DOM diffed, and the commit phase where actual changes in the virtual DOM are committed to the DOM.

Conceptually, react does work in 2 phases. The render phase where components are rendered and the virtual DOM diffed , and the commit phase where actual changes in the virtual DOM are committed to the DOM.

The graphical representation you see on the far right of the profiler represents the number of commits that were made to the DOM during your interaction with the app.

The graphical representation you see on the far right of the profiler represents the number of commits that were made to the DOM during your interaction with the app.

The taller the bar is, the longer it took React to render the components in this commit.

The taller the bar is, the longer it took React to render the components in this commit.

In the example above, the Profiler recorded three commits. That make sense since I clicked the button only 3 times. So, there should be only 3 commits made to the DOM.

In the example above, the Profiler recorded three commits. That make sense since I clicked the button only 3 times. So, there should be only 3 commits made to the DOM.

Also the first commit took much longer than the subsequent two commits.

Also the first commit took much longer than the subsequent two commits.

The three bars represent the different commits made to the DOM, and you can click on any to investigate performance metrics for the particular commit.

The three bars represent the different commits made to the DOM, and you can click on any to investigate performance metrics for the particular commit.

The Flame Chart. (The Flame Chart.)

After a successful recording session, you’ll be presented with a couple different bits of information about your components.

After a successful recording session, you'll be presented with a couple different bits of information about your components.

First, you have 3 tabs representing different groups of information — each relating to the selected commit on the right.

First, you have 3 tabs representing different groups of information — each relating to the selected commit on the right.

The first tab represents a flame chart.

The first tab represents a flame chart.

The flame chart displays information on how long it took your component tree to render.

The flame chart displays information on how long it took your component tree to render.

You’ll notice that each component in your application tree is represented by bars of varying lengths and colors.

You'll notice that each component in your application tree is represented by bars of varying lengths and colors.

The length of a bar defines how long it took the component (and its children) to render.

The length of a bar defines how long it took the component (and its children) to render.

Judging by the bar length, it appears the Provider component took the longest time to render. That make sense since the Provider is the main root component of the app, so the time represented here is the time taken for Provider and all its children to render.

Judging by the bar length, it appears the Provider component took the longest time to render. That make sense since the Provider is the main root component of the app, so the time represented here is the time taken for Provider and all its children to render.

That’s half the story.

That's half the story.

Note that the colors of the bars are different.

Note that the colors of the bars are different.

For example, Provider and a couple other components have a grey color.

For example, Provider and a couple other components have a grey color.

What does that mean?

那是什么意思?

Well, first we are investing the first commit made to the DOM during the interaction with the application.

Well, first we are investing the first commit made to the DOM during the interaction with the application.

The components with a grey color means they weren’t rendered in this commit. If the component is greyed out, it wasn’t rendered in this commit. So, the length of the bar only represents how long it took the component to render previously before this commit i.e. before the interaction with the application.

The components with a grey color means they weren't rendered in this commit. If the component is greyed out, it wasn't rendered in this commit. So, the length of the bar only represents how long it took the component to render previously before this commit ie before the interaction with the application.

If you think about it, that’s reasonable.

If you think about it, that's reasonable.

On careful examination, you’ll see that the only component with a different flame chart color here is the Clap component.

On careful examination, you'll see that the only component with a different flame chart color here is the Clap component.

This component represents the Medium clap button that was clicked.

This component represents the Medium clap button that was clicked.

A yellow bar means the component took the most time to render in this commit.

A yellow bar means the component took the most time to render in this commit.

Well, no other component is coloured which means the Clap button was the only component re-rendered in this commit.

Well, no other component is coloured which means the Clap button was the only component re-rendered in this commit.

That’s perfect!

那很完美!

You don’t want to click the Clap button and have a different component being re-rendered. That’ll be a performance hit right there.

You don't want to click the Clap button and have a different component being re-rendered. That'll be a performance hit right there.

In more complex applications, you’ll find flame charts with not just yellow and grey bars. You’ll find some with blue bars.

In more complex applications, you'll find flame charts with not just yellow and grey bars. You'll find some with blue bars.

What’s worth noting is that, yellow longer bars took more time to render, followed by the blue ones, and finally grey bars weren’t re-rendered in the particular commit being viewed.

What's worth noting is that, yellow longer bars took more time to render, followed by the blue ones, and finally grey bars weren't re-rendered in the particular commit being viewed.

It’s also possible to click on a particular bar to view more information on why it rendered or not i.e. the props and state passed to the component.

It's also possible to click on a particular bar to view more information on why it rendered or not ie the props and state passed to the component.

While zoomed in, you can also click the commit bars on top to see the difference in props or state across each commit render.

While zoomed in, you can also click the commit bars on top to see the difference in props or state across each commit render.

The Ranked Chart. (The Ranked Chart.)

Once you understand how the flame chart works, the ranked chart becomes a walk in the park.

Once you understand how the flame chart works, the ranked chart becomes a walk in the park.

The second tab option refers to the ranked chart.

The second tab option refers to the ranked chart.

The ranked chart displays every component that was rendered in the commit being viewed. It displays this components ranked from top to bottom — with component which took more time to render at the top.

The ranked chart displays every component that was rendered in the commit being viewed. It displays this components ranked from top to bottom — with component which took more time to render at the top.

In this example, we have just the Clap component displayed in the ranked chart view. That’s okay as we only expect the Clap component to be re-rendered upon clicking.

In this example, we have just the Clap component displayed in the ranked chart view. That's okay as we only expect the Clap component to be re-rendered upon clicking.

A more complex ranked chart may look like this:

A more complex ranked chart may look like this:

You can see how the longer yellow bars are right there at the top, and shorter blue bars at the bottom. If you look carefully, you’ll notice that the colors fade as you go from top to bottom. From more yellow bars to pale yellow bars, to light blue bars and finally blue bars.

You can see how the longer yellow bars are right there at the top, and shorter blue bars at the bottom. If you look carefully, you'll notice that the colors fade as you go from top to bottom. From more yellow bars to pale yellow bars, to light blue bars and finally blue bars.

Component Chart. (Component Chart.)

Whenever you zoom into a component within the Profiler i.e. by clicking its associated bar, a new option on the right pops up.

Whenever you zoom into a component within the Profiler ie by clicking its associated bar, a new option on the right pops up.

Clicking this button will bring you to what’s called the component chart.

Clicking this button will bring you to what's called the component chart.

The component chart displays how many times a particular component was re-rendered during the recorded interaction.

The component chart displays how many times a particular component was re-rendered during the recorded interaction.

In this example, I can click the button to view chart for the Clap component.

In this example, I can click the button to view chart for the Clap component.

This shows three bars representing the three times the Clap component was re-rendered. If I saw a fourth bar here, I’d begin to get worried as I only clicked three times and expected only three re-renders.

This shows three bars representing the three times the Clap component was re-rendered. If I saw a fourth bar here, I'd begin to get worried as I only clicked three times and expected only three re-renders.

If the selected component didn’t re-render at all, you’ll be presented with the empty screen below:

If the selected component didn't re-render at all, you'll be presented with the empty screen below:

NB: you can either view the component chart by clicking the button on the far right, or by double clicking a component bar from the flame or ranked chart.

NB : you can either view the component chart by clicking the button on the far right, or by double clicking a component bar from the flame or ranked chart.

Interactions. (Interactions.)

There’s one final tab in the profiler, and by default it displays an empty screen:

There's one final tab in the profiler, and by default it displays an empty screen:

Interactions let you tag actions performed during a recording session with a string identifier so they can be monitored and tracked within the profiling results.

Interactions let you tag actions performed during a recording session with a string identifier so they can be monitored and tracked within the profiling results.

The API for this is unstable, but here’s how to enable interactions in your profiling results.

The API for this is unstable, but here's how to enable interactions in your profiling results.

First install the scheduler module. From your terminal, run yarn add scheduler within the application directory .

First install the scheduler module. From your terminal, run yarn add scheduler within the application directory .

Once the installation is done, you need to use unstable_trace function exported by the module as shown below:

Once the installation is done, you need to use unstable_trace function exported by the module as shown below:

import { unstable_trace as trace } from 'scheduler/tracing'

The function is exported as unstable_trace but you can rename it in the import statement as I have done above.

The function is exported as unstable_trace but you can rename it in the import statement as I have done above.

The trace function has a signature similar to this:

The trace function has a signature similar to this:

trace("string identifier", timestamp, () = {})

It takes a string identifier, timestamp and a callback function. Ideally, you track whatever interaction you want by passing it into the callback.

It takes a string identifier, timestamp and a callback function. Ideally, you track whatever interaction you want by passing it into the callback.

For example, I have gone ahead to do this in the fake-medium application:

For example, I have gone ahead to do this in the fake-medium application:

// before _handleClick () {   // do something}
// after _handleClick () {   trace("Clap was clicked!", window.performace.now(), () => {  	  // do something   })}

The medium clap when clicked calls the _handleClick method. Now, I’ve wrapped the functionality within the trace method.

The medium clap when clicked calls the _handleClick method. Now, I've wrapped the functionality within the trace method.

Here’s what happens when the profiling result is now viewed:

Here's what happens when the profiling result is now viewed:

Clicking three times now records 3 interactions and you can click on any of the interactions to view more details about them.

Clicking three times now records 3 interactions and you can click on any of the interactions to view more details about them.

The interactions will also show up in the flame and ranked charts.

The interactions will also show up in the flame and ranked charts.

Example: Identifying Performance BottleNecks in the Bank Application. (Example: Identifying Performance BottleNecks in the Bank Application.)

Hey John, what have you done?!!!”, said Mia as she stumped into John’s office.

Hey John, what have you done?!!! ”, said Mia as she stumped into John's office.

I was just profiling the bank application, and it’s so not performant”, she added.

I was just profiling the bank application, and it's so not performant”, she added.

John wasn’t surprised. He had not spent a lot of time thinking about performance, but with Mia up in his face, he began to have a rethink.

John wasn't surprised. He had not spent a lot of time thinking about performance, but with Mia up in his face, he began to have a rethink.

Okay, I’ll have a look and fix whatever bottlenecks I find. Can we do that together?”, John said while thinking to himself how much help Mia would be since she spotted the problem anyway.

Okay, I'll have a look and fix whatever bottlenecks I find. Can we do that together? ”, John said while thinking to himself how much help Mia would be since she spotted the problem anyway.

Oh, sure”, she retorted.

Oh, sure ”, she retorted.

After spending a couple hours, they found and fixed a coupe of performance bottlenecks in the application.

After spending a couple hours, they found and fixed a coupe of performance bottlenecks in the application.

What did they do? What measures were taken?

他们做了什么? What measures were taken?

In this example, we’ll spin up the bank application and pick up from where we stopped in the earlier chapter. This time we’ll fix the performance bottlenecks within the application.

In this example, we'll spin up the bank application and pick up from where we stopped in the earlier chapter. This time we'll fix the performance bottlenecks within the application.

Here’s what the bank app looks like again:

Here's what the bank app looks like again:

Noting the Expected Behaviour. (Noting the Expected Behaviour.)

When I need to profile an application, specifically during a certain interaction with an app, I like to set the baseline for what I expect in terms of performance. This sort of expectation helps you retain your focus as you delve into interpreting the results from the Profiler.

When I need to profile an application, specifically during a certain interaction with an app, I like to set the baseline for what I expect in terms of performance. This sort of expectation helps you retain your focus as you delve into interpreting the results from the Profiler.

Let’s consider the bank application we want to profile. The interaction in the bank app is simple. You click a set of buttons, and the withdrawal amount is updated.

Let's consider the bank application we want to profile. The interaction in the bank app is simple. You click a set of buttons, and the withdrawal amount is updated.

Now, what would you consider the expected behaviour of this app with respect to re-renders and updates?

Now, what would you consider the expected behaviour of this app with respect to re-renders and updates?

Well, for me, it’s quite simple.

Well, for me, it's quite simple.

The only part of the app visually changing is the withdrawal amount. Before going into the profiling session, my expectation for a performant application will be that no unnecessary components are re-rendered, just the component responsible for updating the total amount.

The only part of the app visually changing is the withdrawal amount. Before going into the profiling session, my expectation for a performant application will be that no unnecessary components are re-rendered, just the component responsible for updating the total amount.

Give or take, I’ll expect just the TotalAmount component to be re-rendered, or any other component directly connected with that update.

Give or take, I'll expect just the TotalAmount component to be re-rendered, or any other component directly connected with that update.

With this expectation set, let’s go on and profile the application.

With this expectation set, let's go on and profile the application.

The steps are the same as discussed earlier. You open your devtools, record an interaction session, and begin to interpret the results.

The steps are the same as discussed earlier. You open your devtools , record an interaction session, and begin to interpret the results.

Now, I have gone ahead to record a session. In this session, all I did was click the “withdraw $10,000” button 3 times.

Now, I have gone ahead to record a session. In this session, all I did was click the “withdraw $10,000” button 3 times.

Here’s the flame chart from the profiling session:

Here's the flame chart from the profiling session:

Oh my! From the chart above, so many components were re-rendered. You see the many bar colours represented in the flame chart ?

Oh my! From the chart above, so many components were re-rendered. You see the many bar colours represented in the flame chart ?

This is far from ideal, so let’s begin to fix the problem.

This is far from ideal, so let's begin to fix the problem.

Interpreting the Flame chart. (Interpreting the Flame chart.)

First let’s consider what’s likely the root of the problem here. By default, whenever a Provider has its value changed, every child component is forced to re-render. That’s how the Consumer gets the latest values from the context object and stays in sync.

First let's consider what's likely the root of the problem here. By default, whenever a Provider has its value changed, every child component is forced to re-render. That's how the Consumer gets the latest values from the context object and stays in sync.

The problem here is that every component apart from Root is a child of Provider — and they all get re-rendered needlessly!

The problem here is that every component apart from Root is a child of Provider — and they all get re-rendered needlessly!

So, what can we do about this?

So, what can we do about this?

Some of the child components don’t need to be re-rendered as they are not directly connected with the change.

Some of the child components don't need to be re-rendered as they are not directly connected with the change.

First, let’s consider the first child component, App.

First, let's consider the first child component, App .

The App component doesn’t receive any prop and it only manages the state value showBalance.

The App component doesn't receive any prop and it only manages the state value showBalance .

class App extends Component {  state = {    showBalance: false  }  displayBalance = () => {    this.setState({ showBalance: true })  }  render () {    const { showBalance } = this.state    ...  }}

It isn’t directly connected with the change, and it’s pointless to re-render this component.

It isn't directly connected with the change, and it's pointless to re-render this component.

Let’s fix this by making it a PureComponent.

Let's fix this by making it a PureComponent .

// before class App extends Component {  state = {    showBalance: false  } ... }// after class App extends PureComponent {  state = {    showBalance: false  } ... }

Having made App a PureComponent, did we make any decent progress?

Having made App a PureComponent , did we make any decent progress?

Well, have a look at the new flame chart generated after that simple (one-liner) change.

Well, have a look at the new flame chart generated after that simple (one-liner) change.

Can you see that?

你看到了吗?

A lot of App’s children aren’t re-rendered needlessly, and we have a more sane flame graph now.

A lot of App's children aren't re-rendered needlessly, and we have a more sane flame graph now.

Great!

大!

Profile Different Interactions. (Profile Different Interactions.)

It’s easy to assume that because we had fewer re-renders in the “withdraw amount” interaction we now have a performant app.

It's easy to assume that because we had fewer re-renders in the “withdraw amount” interaction we now have a performant app.

That’s not correct.

那是不对的。

App’s now a PureComponent, but what happens when App gets rendered owing to a state change?

App's now a PureComponent , but what happens when App gets rendered owing to a state change?

Well, let’s profile a different interaction. This time, load up the application and profile the interaction for viewing an account balance.

Well, let's profile a different interaction. This time, load up the application and profile the interaction for viewing an account balance.

If you go ahead and profile the interaction, we get a completely different result.

If you go ahead and profile the interaction, we get a completely different result.

Now, what’s changed?

Now, what's changed?

From the flame graph above, every child component of App as been re-rendered. They all had nothing to do with this visual update, so those are wasted rendered.

From the flame graph above, every child component of App as been re-rendered. They all had nothing to do with this visual update, so those are wasted rendered.

NB: If you need to check the hierarchy of components more clearly, remember you can always click the elements tab:

NB: If you need to check the hierarchy of components more clearly, remember you can always click the elements tab:

Well, since these child components are functional components, let’s use React.memo to memoize the render results so they don’t change except there’s a change in props.

Well, since these child components are functional components, let's use React.memo to memoize the render results so they don't change except there's a change in props.

// User.jsimport { memo } from 'react'const User = memo(({ profilePic }) => {  ...})
// ViewAccountBalance.jsimport { memo } from 'react'const ViewAccountBalance = memo(({ showBalance, displayBalance }) => {      ...})
// WithdrawButton.jsimport { memo } from 'react'const WithdrawButton = memo(({ amount }) => {    ...  )})

Now, when you do that and re-profile the interaction, we get a much nicer flame chart:

Now, when you do that and re-profile the interaction, we get a much nicer flame chart:

Now, only ViewAccountBalance and other child components are re-rendered. That’s okay.

Now, only ViewAccountBalance and other child components are re-rendered. 没关系。

When you view your flame chart i.e if you’re following along, you may see something slightly different.

When you view your flame chart ie if you're following along, you may see something slightly different.

The names of the component may not be shown. You get the generic name Memo and it becomes difficult to track which component is what.

The names of the component may not be shown. You get the generic name Memo and it becomes difficult to track which component is what.

To change this, set the displayName property for the memoized components.

To change this, set the displayName property for the memoized components.

Below’s an example.

Below's an example.

// ViewAccountBalance.jsconst ViewAccountBalance = memo(({ showBalance, displayBalance }) => {  ...})
// set the displayName hereViewAccountBalance.displayName = 'ViewAccountBalance'

You go ahead and do this for all the memoized functional components.

You go ahead and do this for all the memoized functional components.

The Provider Value. (The Provider Value.)

We’re pretty much done with resolving the performance leaks in the application, however, there’s one more thing to do.

We're pretty much done with resolving the performance leaks in the application, however, there's one more thing to do.

The effect isn’t very obvious in this application, but will come handy as you face more cases in the real world such as in situations where a Provider is nested within other components.

The effect isn't very obvious in this application, but will come handy as you face more cases in the real world such as in situations where a Provider is nested within other components.

The implementation of the Provider in the bank application had the following:

The implementation of the Provider in the bank application had the following:

...<Provider    value={{       user: loggedInUser,       handleLogin: this.handleLogin       handleWithdrawal: this.handleWithdrawal     }}   >  {this.props.children}</Provider>...

The problem here is that we’re passing a new object to the value prop every single time. A better solution will be to keep a reference to these values via state. e.g.

The problem here is that we're passing a new object to the value prop every single time. A better solution will be to keep a reference to these values via state. 例如

<Provider value={this.state}>	{this.props.children}</Provider>

Doing this requires a bit of refactoring as shown below:

Doing this requires a bit of refactoring as shown below:

// context/UserContext.jsclass UserProvider extends Component {  constructor () {    super()    this.state = {      user: null,      handleLogin: this.handleLogin,      handleWithdrawal: this.handleWithdrawal    }  }  ...  render () {    return <Provider value={this.state}>  		{this.props.children}	</Provider>  }}

Be sure to look in the associated code folder if you need more clarity on this.

Be sure to look in the associated code folder if you need more clarity on this.

结论。 (Conclusion.)

Profiling applications and identifying performance leaks is fun and rewarding. I hope you’ve gained relevant knowledge in this section.

Profiling applications and identifying performance leaks is fun and rewarding. I hope you've gained relevant knowledge in this section.

Chapter 6: Lazy Loading with React.Lazy and Suspense. (Chapter 6: Lazy Loading with React.Lazy and Suspense.)

Hey John, we need to look into lazy loading some modules in the Benny application”, says Tunde, John’s manager.

Hey John, we need to look into lazy loading some modules in the Benny application ”, says Tunde, John's manager.

John’s had great feedback from his manager for the past few months. Every now and then Tunde storms into the office with a new project idea. Today, it’s lazy loading with React.Lazy and Suspense.

John's had great feedback from his manager for the past few months. Every now and then Tunde storms into the office with a new project idea. Today, it's lazy loading with React.Lazy and Suspense .

John’s never lazy loaded a module with React.Lazy and Suspense before now. This is all new to him, so he ventures into a quick study to deliver on what his manager has requested.

John's never lazy loaded a module with React.Lazy and Suspense before now. This is all new to him, so he ventures into a quick study to deliver on what his manager has requested.

What is Lazy Loading? (What is Lazy Loading?)

When you bundle your application, you likely have the entire application bundled in one large chunk.

When you bundle your application, you likely have the entire application bundled in one large chunk.

As your app grows, so does the bundle.

As your app grows, so does the bundle.

To understand lazy loading, here’s the specific use case Tunde had in mind when he discussed with John.

To understand lazy loading, here's the specific use case Tunde had in mind when he discussed with John.

Hey John, do you remember the Benny app has an initial home screen?”, said Tunde.

Hey John, do you remember the Benny app has an initial home screen? ”, said Tunde.

By initial home scree, Tunde was referring to this:

By initial home scree, Tunde was referring to this:

This is the first screen the user encounters when they visit the Benny game. To begin playing the game, you must click the “Start Game” button to be redirected to the actual game scene.

This is the first screen the user encounters when they visit the Benny game. To begin playing the game, you must click the “Start Game” button to be redirected to the actual game scene.

John, the problem here is that we’ve bundled all our React components together and are all served to the user on this page”.

John, the problem here is that we've bundled all our React components together and are all served to the user on this page ”.

Oh, I see where you’re going”, said John.

Oh, I see where you're going ”, said John.

Instead of loading the GameScene component and its associated assets, we could defer the loading of those until the user actually clicks ’Start Game’, huh?”, said John.

Instead of loading the GameScene component and its associated assets, we could defer the loading of those until the user actually clicks 'Start Game', huh? ”, said John.

And Tunde agreed with a resounding “Yes, that’s exactly what I mean”.

And Tunde agreed with a resounding “ Yes, that's exactly what I mean ”.

Lay loading refers to deferring the loading of a particular resource until much later, usually when a user makes an interaction that demands the resource to be actually loaded. In some cases it could also mean preloading a resource.

Lay loading refers to deferring the loading of a particular resource until much later, usually when a user makes an interaction that demands the resource to be actually loaded. In some cases it could also mean preloading a resource.

Essentially, the user doesn’t get the lazy loaded bundle served to them initially, rather it is fetched much later at runtime.

Essentially, the user doesn't get the lazy loaded bundle served to them initially, rather it is fetched much later at runtime.

This is great for performance optimisations, initial load speed etc.

This is great for performance optimisations, initial load speed etc.

React makes lazy loading possible by employing the dynamic import syntax.

React makes lazy loading possible by employing the dynamic import syntax.

Dynamic imports refer to a tc39 syntax proposal for javascript, however, with transpilers like Babel, we can use this syntax today.

Dynamic imports refer to a tc39 syntax proposal for javascript , however, with transpilers like Babel, we can use this syntax today.

The typical, static way of importing a module looks like this:

The typical, static way of importing a module looks like this:

import { myModule } from 'awesome-module'

While this is desirable in many cases, the syntax doesn’t allow for dynamically loading a module at runtime.

While this is desirable in many cases, the syntax doesn't allow for dynamically loading a module at runtime.

Being able to dynamically load a part of a Javascript application at runtime makes way for interesting use cases such as loading a resource based on a user’s language (a factor that can only be determined at runtime), or only loading some code just when it is likely to be used by the user (performance gains).

Being able to dynamically load a part of a Javascript application at runtime makes way for interesting use cases such as loading a resource based on a user's language (a factor that can only be determined at runtime), or only loading some code just when it is likely to be used by the user (performance gains).

For these reasons (and more) there’s a proposal for introducing the dynamic import syntax to Javascript.

For these reasons (and more) there's a proposal for introducing the dynamic import syntax to Javascript.

Here’s what the syntax looks like:

Here's what the syntax looks like:

import('path-to-awesome-module')

It has a syntax similar to a function, but is really not a function. It doesn’t inherit from Funtion.proptotype and you can’t invoke methods such as call and apply.

It has a syntax similar to a function, but is really not a function. It doesn't inherit from Funtion.proptotype and you can't invoke methods such as call and apply .

The returned result from the dynamic import call is a promise which is resolved with the imported module.

The returned result from the dynamic import call is a promise which is resolved with the imported module.

import('path-to-awesome-module')	.then(module => {     // do something with the module here e.g. module.default() to invoke the default export of the module. })
Using React.lazy and Suspense. (Using React.lazy and Suspense.)

React.lazy and Suspense make using dynamic imports in a React application so easy.

React.lazy and Suspense make using dynamic imports in a React application so easy.

For example, consider the demo code for the Benny application below:

For example, consider the demo code for the Benny application below:

import React from 'react'import Benny from './Benny'import Scene from './Scene'import GameInstructions from './GameInstructions'
class Game extends Component {  state = {    startGame: false  }  render () {    return !this.state.startGame ?         <GameInstructions /> : 		<Scene />  }}export default Game;

Based on the state property startGame, either the GameInstructions or Scene component is rendered when the user clicks the “Start Game” button.

Based on the state property startGame , either the GameInstructions or Scene component is rendered when the user clicks the “Start Game” button.

GameInstructions represents the home page of the game and Scene the entire scene of the game itself.

GameInstructions represents the home page of the game and Scene the entire scene of the game itself.

In this implementation, GameInstructions and Scene will be bundled together in the same Javascript resource.

In this implementation, GameInstructions and Scene will be bundled together in the same Javascript resource.

Consequently, even when the user hasn’t shown intent to start playing the game, we would have loaded and sent to the user, the complex Scene component which contains all the logic for the game scene.

Consequently, even when the user hasn't shown intent to start playing the game, we would have loaded and sent to the user, the complex Scene component which contains all the logic for the game scene.

So, what do we do?

那么我们该怎么办?

Let’s defer the loading of the Scene component.

Let's defer the loading of the Scene component.

Here’s how easy React.lazy makes that.

Here's how easy React.lazy makes that.

// before import Scene from './Scene'
// now const Scene = React.lazy(() => import('./Scene'))

React.lazy takes a function that must call a dynamic import. In this case, the dynamic import call is import('./Scene').

React.lazy takes a function that must call a dynamic import. In this case, the dynamic import call is import('./Scene') .

Note that React.lazy expects the dynamically loaded module to have a default export containing a React component.

Note that React.lazy expects the dynamically loaded module to have a default export containing a React component.

With the Scene component now dynamically loaded, when the application is bundled for production, a separate module (or javascript file) will be created for the Scene dynamic import.

With the Scene component now dynamically loaded, when the application is bundled for production, a separate module (or javascript file) will be created for the Scene dynamic import.

When the app loads, this javascript file won’t be sent to the user. However, if they click the “Start Game” button and show intent to load the Scene component, a network request would be made to fetch the resource from the remote server.

When the app loads, this javascript file won't be sent to the user. However, if they click the “Start Game” button and show intent to load the Scene component, a network request would be made to fetch the resource from the remote server.

Now, fetching from the server introduces some latency. To handle this, wrap the Scene component in a Suspense component to show a fallback for when the resource is being fetched.

Now, fetching from the server introduces some latency. To handle this, wrap the Scene component in a Suspense component to show a fallback for when the resource is being fetched.

Here’s what I mean:

这就是我的意思:

import { Suspense } from 'react'const Scene = React.lazy(() => import('./Scene'))
class Game extends Component {  state = {    startGame: false  }  render () {    return !this.state.startGame ?         <GameInstructions /> : 		// look here		<;Suspense fallback="<div>loading ...</div>">		  <Scene />		</Suspense>  }}export default Game;

Now, when the network request is initiated to fetch the Scene resource, we’ll show a “loading…” fallback courtesy the Suspense component.

Now, when the network request is initiated to fetch the Scene resource, we'll show a “loading…” fallback courtesy the Suspense component.

Suspense takes a fallback prop which can be a markup as shown here, or a full blown React component e.g. a custom loader.

Suspense takes a fallback prop which can be a markup as shown here, or a full blown React component eg a custom loader.

With React.lazy and Suspense you can suspend the fetching of a component until much later, and show a fallback for when the resource is being fetched.

With React.lazy and Suspense you can suspend the fetching of a component until much later, and show a fallback for when the resource is being fetched.

How convenient.

How convenient.

Also, you can place the Suspense component anywhere above the lazy loaded component. In this case the Scene component.

Also, you can place the Suspense component anywhere above the lazy loaded component. In this case the Scene component.

If you also had multiple lazy loaded components, you could wrap them in a single Suspense component or multiple, depending on your specific use case.

If you also had multiple lazy loaded components, you could wrap them in a single Suspense component or multiple, depending on your specific use case.

Handling Errors. (Handling Errors.)

In the real-world, things break often, right?

In the real-world, things break often, right?

It’s possible that in the process of fetching the lazy loaded resource, a network error occurs.

It's possible that in the process of fetching the lazy loaded resource, a network error occurs.

To handle such case, be sure to wrap your lazy loaded components in an Error Boundary.

To handle such case, be sure to wrap your lazy loaded components in an Error Boundary .

Remember error boundaries from the Lifecycle method chapter?

Remember error boundaries from the Lifecycle method chapter?

Here’s an example:

这是一个例子:

import { Suspense } from 'react'import MyErrorBoundary from './MyErrorBoundary'const Scene = React.lazy(() => import('./Scene'))class Game extends Component {  state = {    startGame: false  }
render () {    return &lt;MyErrorBoundary>         {!this.state.startGame ?            <GameInstructions /> : 		   <Suspense fallback="loading ...">		     <Scene />;		   </Suspense>}		</MyErrorBoundary>  }}export default Game;

Now, if an error occurs while fetching the lazy loaded resource, it’ll be graciously handled by the error boundary.

Now, if an error occurs while fetching the lazy loaded resource, it'll be graciously handled by the error boundary.

No named exports. (No named exports.)

If you remember from the section above, I did mention that React.lazy expects the dynamic import statement to include a module with a default export being a React component.

If you remember from the section above, I did mention that React.lazy expects the dynamic import statement to include a module with a default export being a React component.

At the moment, React.lazy doesn’t support named exports. That’s not entirely a bad thing, as it keeps tree shaking working so you don’t import actual unused modules.

At the moment, React.lazy doesn't support named exports. That's not entirely a bad thing, as it keeps tree shaking working so you don't import actual unused modules.

Consider the following module:

Consider the following module:

// MyAwesomeComponents.js export const AwesomeA = () => <div> I am awesome </div> export const AwesomeB = () => <div> I am awesome </div> export const AwesomeC = () => <div> I am awesome </div>

Now, if you attempt to use React.lazy with a dynamic import of the module above, you’ll get an error.

Now, if you attempt to use React.lazy with a dynamic import of the module above, you'll get an error.

// SomeWhereElse.js const Awesome = React.lazy(() => import('./MyAwesomeComponents'))

That won’t work since there’s no default export in the MyAwesomeComponents.js module.

That won't work since there's no default export in the MyAwesomeComponents.js module.

A workaround would be to create some other module that exports one of the components as a default.

A workaround would be to create some other module that exports one of the components as a default.

For example, if I was interested in lazy loading the AwesomeA component from the MyAwesomeComponents.js module, I could create a new module like this:

For example, if I was interested in lazy loading the AwesomeA component from the MyAwesomeComponents.js module, I could create a new module like this:

// AwesomeA.js export { AwesomeA as default } from './MyAwesomeComponents'

Then I can can go ahead to effectively use React.lazy as follows:

Then I can can go ahead to effectively use React.lazy as follows:

// SomeWhereElse.jsconst AwesomeA = React.lazy(() => import('AwesomeA'))

Problem solved!

问题解决了!

Code splitting routes. (Code splitting routes.)

Code splitting advocates that instead of sending this large chunk of code to the user at once, you may dynamically send chunks to the user when they need it.

Code splitting advocates that instead of sending this large chunk of code to the user at once, you may dynamically send chunks to the user when they need it.

We had looked at component based code splitting in the earlier examples, but another common approach is with route based code splitting.

We had looked at component based code splitting in the earlier examples, but another common approach is with route based code splitting.

In this method, the code is split into chunks based on the routes in the application.

In this method, the code is split into chunks based on the routes in the application.

We could also take our knowledge of lazy loading one step further by code splitting routes.

We could also take our knowledge of lazy loading one step further by code splitting routes.

Consider a typical React app that uses react-router for route matching.

Consider a typical React app that uses react-router for route matching.

const App = () => (  <Router>      <Switch>        <Route exact path="/" component={Home}/>        <Route path="/about" component={About}/>      </Switch>  </Router>);

We could lazy load the Home and About components so they are only fetched when the user hits the associated routes.

We could lazy load the Home and About components so they are only fetched when the user hits the associated routes.

Here’s how with React.Lazy and Suspense.

Here's how with React.Lazy and Suspense .

// lazy load the route componentsconst Home = React.lazy(() => import('./Home'))const About = React.lazy(() => import('./About'))
// Provide a fallback with Suspenseconst App = () => (  <Router&gt;    <Suspense fallback={<div>Loading...</div>}>      <Switch>        <Route exact path="/" component={Home}/>        <Route path="/about" component={About}/>      </Switch>    </Suspense>  </Router>);

Easy, huh?

Easy, huh?

We’ve discussed how React.Lazy and Suspense works, but under the hood, the actual code splitting i.e. generating separate bundles for different modules is done by a bundler, for example Webpack.

We've discussed how React.Lazy and Suspense works, but under the hood, the actual code splitting ie generating separate bundles for different modules is done by a bundler, for example Webpack .

If you use create-react-app, Gatsby or Next.js then you have this already set up for you.

If you use create-react-app , Gatsby or Next.js then you have this already set up for you.

Setting this up yourself is also easy, you just need to tweak your Webpack config a little bit.

Setting this up yourself is also easy, you just need to tweak your Webpack config a little bit.

The official Webpack documentation has an entire guide on this. The guide may be worth checking if you’re handling the budding configurations in your application yourself.

The official Webpack documentation has an entire guide on this. The guide may be worth checking if you're handling the budding configurations in your application yourself.

Example: Adding Lazy Loading to the Bank App. (Example: Adding Lazy Loading to the Bank App.)

We can add some lazy loading to the bank application we saw from Chapter 2.

We can add some lazy loading to the bank application we saw from Chapter 2.

Consider the Root component of the application:

Consider the Root component of the application:

const Root = () => (  <UserProvider>    <UserConsumer>      {({ user, handleLogin }) =>        user ? <App /> : <Login handleLogin={handleLogin} />      }    </UserConsumer>  </UserProvider>)

When a user isn’t logged in we display the login page, and the App component only when the user is logged in.

When a user isn't logged in we display the login page, and the App component only when the user is logged in.

We could lazy load the App component, right?

We could lazy load the App component, right?

This is very easy. You use the dynamic import syntax with React.lazy and wrap the App component in a Suspense component.

这很容易。 You use the dynamic import syntax with React.lazy and wrap the App component in a Suspense component.

Here’s how:

这是如何做:

...const App = React.lazy(() => import('./containers/App'))const Root = () => (  ...  <Suspense fallback='loading...'>     <App /&gt;  </Suspense>)

Now, if you throttle your network connection to simulate Slow 3G, you’ll see the intermediate “loading…” text after logging in.

Now, if you throttle your network connection to simulate Slow 3G, you'll see the intermediate “loading…” text after logging in.

结论。 (Conclusion.)

React.lazy and Suspense are great, and so intuitive to work with, however, they do not support server side rendering yet.

React.lazy and Suspense are great, and so intuitive to work with, however, they do not support server side rendering yet.

It’s likely this will change in the future, but in the mean time, if you care about SSR, using react-loadable is your best bet for lazy loading React components.

It's likely this will change in the future, but in the mean time, if you care about SSR, using react-loadable is your best bet for lazy loading React components.

Chapter 7: Hooks — Building Simpler React Apps. (Chapter 7: Hooks — Building Simpler React Apps.)

For the past 3 years John’s been writing React apps, functional components have mostly been dumb.

For the past 3 years John's been writing React apps, functional components have mostly been dumb.

If you wanted local state or some other complex side effects, you had to reach out to class component. You either painfully refactor your functional components to class components or nothing else.

If you wanted local state or some other complex side effects, you had to reach out to class component. You either painfully refactor your functional components to class components or nothing else.

It’s a bright Thursday afternoon, and while having lunch, Mia introduces John to Hooks.

It's a bright Thursday afternoon, and while having lunch, Mia introduces John to Hooks.

She speaks so enthusiastically, it piques John’s interest.

She speaks so enthusiastically, it piques John's interest.

Of all the things Mia said, one thing struck John. “With hooks, functional components become just as powerful (if not more powerful) than your typical class components”.

Of all the things Mia said, one thing struck John. “ With hooks, functional components become just as powerful (if not more powerful) than your typical class components ”.

That’s a bold statement from Mia.

That's a bold statement from Mia.

So, let’s consider what hooks are.

So, let's consider what hooks are.

Introducing Hooks. (Introducing Hooks.)

Early this year, 2019, the React team released a new addition, hooks, to React in version 16.8.0.

Early this year, 2019, the React team released a new addition, hooks, to React in version 16.8.0.

If React were a big bowl of candies, then hooks are the latest additions, very chewy candies with great taste!

如果说React是一大碗糖果,那么钩子就是最新的添加,非常有嚼劲的糖果,味道很好!

So, what exactly do hooks mean? And why are they worth your time?

那么,钩子到底是什么意思? 为什么他们值得您花时间?

One of the main reasons hooks were added to React is to offer a more powerful and expressive way to write (and share) functionality between components.

向React添加钩子的主要原因之一是提供了一种更强大,更富表现力的方式来在组件之间编写(和共享)功能。

In the longer term, we expect Hooks to be the primary way people write React components — React Team

从长远来看,我们希望Hooks成为人们编写React组件的主要方式-React Team

If hooks are going to be that important, why not learn about them in a fun way!

如果钩子变得如此重要,为什么不以有趣的方式了解它们呢!

The Candy Bowl. (The Candy Bowl.)

Consider React to be a beautiful bowl of candy.

将React视为一碗漂亮的糖果。

The bowl of candy has been incredibly helpful to people around the world.

这碗糖果对世界各地的人们都有不可思议的帮助。

The people who made this bowl of candy realized that some of the candies in the bowl weren’t doing people much good.

The people who made this bowl of candy realized that some of the candies in the bowl weren't doing people much good.

A couple of the candies tasted great, yes! But they brought about some complexity when people ate them — think render props and higher order components?

是的,有几个糖果尝起来很棒! 但是当人们食用它们时,它们带来了一些复杂性-想想渲染道具和高阶组件?

So, what did they do?

那么他们做了什么?

They did the right thing — not throwing away all the previous candies, but making new sets of candies.

他们做了正确的事情-不会扔掉所有以前的糖果,而是制作新的糖果。

These candies were called Hooks.

这些糖果被称为胡克斯

These candies exist for one purpose: to make it easier for you to do the things you are already doing.

These candies exist for one purpose: to make it easier for you to do the things you are already doing .

These candies aren’t super special. In fact, as you begin to eat them you’ll realize they taste familiar — they are just Javascript functions!

这些糖果不是超级特别。 实际上,当您开始吃它们时,您会意识到它们听起来很熟悉-它们只是Javascript函数

As with all good candies, these 10 new candies all have their unique names. Though they are collectively called hooks.

As with all good candies, these 10 new candies all have their unique names. 虽然它们统称为钩子

Their names always begin with the three letter word, use … e.g. useState, useEffect etc.

Their names always begin with the three letter word, use … eg useState , useEffect etc.

Just like chocolate, these 10 candies all share some of the same ingredients. Knowing how one tastes, helps you relate to the other.

就像巧克力一样,这10个糖果都共享一些相同的成分。 知道一种口味,可以帮助您与另一种相处。

Sounds fun? Now let’s have these candies.

听起来很有趣? 现在让我们吃这些糖果。

The State Hook. (The State Hook.)

As stated earlier, hooks are functions. Officially, there are 10 of them. 10 new functions that exist to make writing and sharing functionalities in your components a lot more expressive.

如前所述,钩子是函数。 官方上有10个。 现有的10个新功能使组件中的编写和共享功能更具表现力。

The first hook we’ll take a look at is called, useState.

我们将要看的第一个钩子称为useState

For a long time, you couldn’t use the local state in a functional component. Well, not until hooks.

长期以来,您无法在功能组件中使用局部状态。 好吧,直到钩上。

With useState, your functional component can have (and update) local state.

使用useState ,您的功能组件可以具有(和更新)本地状态。

How interesting.

很有意思。

Consider the following counter application:

考虑以下计数器应用程序:

With the Counter component shown below:

With the Counter component shown below:

Simple, huh?

简单吧?

Let me ask you one simple question. Why exactly do we have this component as a Class component?

我问一个简单的问题。 Why exactly do we have this component as a Class component?

Well, the answer is simply because we need to keep track of some local state within the component.

答案很简单,因为我们需要跟踪组件内的某些局部状态。

Now, here’s the same component refactored to a functional component with access to state via the useState hooks.

现在,这里是将相同的组件重构为可以通过useState钩子访问状态的功能组件。

What’s different?

有什么不同?

I’ll walk you through it step by step.

我将逐步指导您。

A functional component doesn’t have all the Class extend ... syntax.

一个功能组件并不具有所有的Class extend ...语法。

function CounterHooks() {}

It also doesn’t require a render method.

It also doesn't require a render method.

function CounterHooks() {    return (      <div>        <h3 className="center">Welcome to the Counter of Life </h3>        <button           className="center-block"           onClick={this.handleClick}> {count} </button>      </div>    ); }

There are two concerns with the code above.

上面的代码有两个问题。

  • You’re not supposed to use the this keyword in function components.

    您不应该在功能组件中使用this关键字。

  • The count state variable hasn’t been defined.

    count状态变量尚未定义。

Extract handleClick to a separate function within the functional component:

提取handleClick到功能组件内的单独功能:

function CounterHooks() {  const handleClick = () => {   }  return (      <div>        <h3 className="center">Welcome to the Counter of Life </h3>        <button           className="center-block"           onClick={handleClick}> {count} </button>      </div>    ); }

Before the refactor, the count variable came from the class component’s state object.

Before the refactor, the count variable came from the class component's state object.

In functional components, and with hooks, that comes from invoking the useState function or hook.

在功能组件中,并带有挂钩,这是通过调用useState函数或挂钩来实现的。

useState is called with one argument, the initial state value e.g. useState(0) where 0 represents the initial state value to be kept track of.

useState is called with one argument, the initial state value eg useState(0) where 0 represents the initial state value to be kept track of.

Invoking this function returns an array with two values.

调用此函数将返回具有两个值的数组。

//? returns an array with 2 values. useState(0)

The first value being the current state value being tracked, and second, a function to update the state value.

The first value being the current state value being tracked, and second, a function to update the state value.

Think of this as some state and setState replica - however, they aren’t quite the same.

可以将其视为statesetState副本-但是它们并不完全相同。

With this new knowledge, here’s useState in action.

有了这些新知识,这里的useState作用。

function CounterHooks() {  // ?   const [count, setCount] = useState(0);  const handleClick = () => {    setCount(count + 1)  }  return (      <div>        <h3 className="center">Welcome to the Counter of Life </h3>        <button           className="center-block"           onClick={handleClick}> {count} </button>      </div>    ); }

There are a few things to note here, apart from the obvious simplicity of the code!

除了代码的明显简单性,这里还需要注意几件事!

One, since invoking useState returns an array of values, the values could be easily destructed into separate values as shown below:

一个,由于调用useState返回一个值数组,因此可以很容易地将这些值分解为单独的值,如下所示:

const [count, setCount] = useState(0);

Also, note how the handleClick function in the refactored code doesn’t need any reference to prevState or anything like that.

另外,请注意,重构代码中的handleClick函数如何不需要任何对prevState引用或类似的引用。

It just calls setCount with the new value, count + 1.

It just calls setCount with the new value, count + 1 .

Simple as it sounds, you’ve built your very first component using hooks. I know it’s a contrived example, but that’s a good start!

听起来很简单,但是您已经使用钩子构建了第一个组件。 我知道这是一个人为的例子,但这是一个好的开始!

NB: it’s also possible to pass a function to the state updater function. This is usually recommended as with setState when a state update depends on a previous value of state e.g. setCount(prevCount => prevCount + 1)

NB : it's also possible to pass a function to the state updater function. This is usually recommended as with setState when a state update depends on a previous value of state eg setCount(prevCount => prevCount + 1)

Multiple useState calls. (Multiple useState calls.)

With class components, we all got used to setting state values in an object whether they contained a single property or more.

With class components, we all got used to setting state values in an object whether they contained a single property or more.

// single property state = {  count: 0}// multiple properties state = { count: 0, time: '07:00'}

With useState you may have noticed a subtle difference.

使用useState您可能已经注意到了细微的差别。

In the example above, we only called useState with the actual initial value. Not an object to hold the value.

在上面的示例中,我们仅使用实际初始值调用useState 。 不是持有价值的对象。

useState(0)

So, what if we wanted to keep track of another state value?

So, what if we wanted to keep track of another state value?

Can multiple useState calls be used?

可以使用多个useState调用吗?

Consider the component below. It’s the same counter application with a twist. This time the counter keeps track of the time of click.

考虑下面的组件。 It's the same counter application with a twist. This time the counter keeps track of the time of click.

As you can see, the hooks usage is quite the same, except for having a new useState call.

如您所见,钩子用法是完全一样的,除了有一个新的useState调用。

const [time, setTime] = useState(new Date())

Now, the time state variable is used in the rendered markup to display the hour, minute and second of the click.

Now, the time state variable is used in the rendered markup to display the hour, minute and second of the click.

<p>     at: {`${time.getHours()} : ${time.getMinutes()} :${time.getSeconds()}`}  </p>

Great!

大!

Object as Initial Values (Object as Initial Values)

Is it possible to use an object with useState as opposed to multiple useState calls?

Is it possible to use an object with useState as opposed to multiple useState calls?

Absolutely!

绝对!

If you choose to do this, you should note that unlike setState calls, the values passed into useState replaces the state value.

如果选择执行此操作,则应注意,与setState调用不同,传递给useState的值将替换状态值。

setState merges object properties but useState replaces the entire value.

setState合并对象属性,但useState替换整个值。

The Effect Hook. (The Effect Hook.)

With class components you’ve likely performed side effects such as logging, fetching data or managing subscriptions.

使用类组件,您可能会产生副作用,例如日志记录,获取数据或管理订阅。

These side effects may be called “effects” for short, and the effect hook, useEffect was created for this purpose.

这些副作用可以简称为“效果”, useEffect创建了效果挂钩useEffect

How’s it used?

如何使用?

Well, the useEffect hook is called by passing it a function within which you can perform your side effects.

好吧,通过向useEffect挂钩传递了一个函数,您可以在其中执行副作用,从而调用它。

Below’s a quick example:

Below's a quick example:

useEffect(() => {  // ? you can perform side effects here  console.log("useEffect first timer here.")})

To useEffect I’ve passed an anonymous function with some side effect called within it.

为了使用useEffect我传递了一个匿名函数,该函数内部具有一些副作用。

The next logical question is, when is the useEffect function invoked?

The next logical question is, when is the useEffect function invoked?

Well, remember that in class components you had lifecycle methods such as componentDidMount and componentDidUpdate.

好吧,请记住,在类组件中,您具有生命周期方法,例如componentDidMountcomponentDidUpdate

Since functional components don’t have these lifecycle methods, useEffect kinda takes their place.

由于功能组件没有这些生命周期方法,因此useEffect 有点替代了它们。

Thus, in the example above, the function within useEffect also known as the effect function, will be invoked when the functional component mounts (componentDidMount) and when the component updates componentDidUpdate).

因此,在上面的示例中,当安装功能组件( componentDidMount )以及组件更新componentDidUpdate时,将调用useEffect的函数(也称为效果函数)。

Here’s that in action.

这就是行动。

By adding the useEffect call above to the counter app, we indeed get the log from the useEffect function.

By adding the useEffect call above to the counter app, we indeed get the log from the useEffect function.

By default, the useEffect function will be called after every render.

By default, the useEffect function will be called after every render.

NB: The useEffect hook isn’t entirely the same as componentDidMount + componentDidUpdate. It can be viewed as such, but the implementation differs with some subtle differences.

注意useEffect挂钩与componentDidMount + componentDidUpdate并不完全相同。 可以这样看,但是实现方式有所不同,但有一些细微的差异。

Passing Array Dependencies. (Passing Array Dependencies.)

It’s interesting that the effect function is invoked every time there’s an update. That’s great, but it’s not always the desired functionality.

It's interesting that the effect function is invoked every time there's an update. 很好,但这并不总是所需的功能。

What if you only want to run the effect function only when the component mounts?

如果只想在组件安装时才运行效果功能怎么办?

That’s a common use case and useEffect takes a second parameter, an array of dependencies to handle this.

这是一个常见的用例, useEffect需要第二个参数,一个依赖项数组来处理这个问题。

If you pass in an empty array, the effect function is run only on mount — subsequent re-renders don’t trigger the effect function.

如果传入空数组,则效果功能仅在安装时运行-后续重新渲染不会触发效果功能。

useEffect(() => {    console.log("useEffect first timer here.")}, [])

If you pass any values into this array, then the effect function will be run on mount, and anytime the values passed are updated. That is, if any of the values are changed, the effected call will re-run.

如果将任何值传递到此数组,则效果功能将在安装时运行,并且在任何时候更新传递的值。 That is, if any of the values are changed, the effected call will re-run.

useEffect(() => {    console.log("useEffect first timer here.")}, [count])

The effect function will be run on mount, and whenever the count function changes.

效果功能将在安装时以及计数功能更改时运行。

What about subscriptions?

订阅呢?

It’s common to subscribe and unsubscribe from certain effects in certain apps.

在某些应用程序中订阅和取消订阅某些效果很常见。

Consider the following:

考虑以下:

useEffect(() => {  const clicked = () => console.log('window clicked');  window.addEventListener('click', clicked);}, [])

In the effect above, upon mounting, a click event listener is attached to the window.

在上面的效果中,安装后,单击事件侦听器将附加到窗口。

How do we unsubscribe from this listener when the component is unmounted?

卸载组件后,我们如何退订此侦听器?

Well, useEffect allows for this.

好吧, useEffect允许这样做。

If you return a function within your effect function, it will be invoked when the component unmounts. This is the perfect place to cancel subscriptions as shown below:

如果您在effect函数中返回一个函数,则在卸载组件时将调用该函数。 这是取消订阅的理想场所,如下所示:

useEffect(() => {    const clicked = () => console.log('window clicked');    window.addEventListener('click', clicked);
return () =>; {      window.removeEventListener('click', clicked)    } }, [])

There’s a lot more you can do with the useEffect hook such as making API calls.

There's a lot more you can do with the useEffect hook such as making API calls.

建立自己的挂钩 (Build Your own Hooks)

From the start of the hooks section we’ve taken (and used) candies from the candy box React provides.

From the start of the hooks section we've taken (and used) candies from the candy box React provides.

However, React also provides a way for you to make your own unique candies — called custom hooks.

但是,React还为您提供了一种制作自己独特的糖果的方法-称为自定义钩子。

So, how does that work?

那么,如何运作?

A custom hook is just a regular function. However, its name must begin with the word, use and if needed, it may call any of the React hooks within itself.

自定义钩子只是一个常规函数。 However, its name must begin with the word, use and if needed, it may call any of the React hooks within itself.

Below’s an example:

下面是一个示例:

钩子规则 (The Rules of Hooks)

There are two rules to adhere to while using hooks.

使用挂钩时要遵守两个规则。

  • Only Call Hooks at the Top Level i.e. not within conditionals, loops or nested functions.

    Only Call Hooks at the Top Level ie not within conditionals, loops or nested functions.

  • Only Call Hooks from React Functions i.e. Functional Components and Custom Hooks.

    仅来自React函数的呼叫挂钩,即功能组件和自定义挂钩。

This ESLint plugin is great to ensure you adhere to these rules within your projects.

这个ESLint 插件非常适合确保您在项目中遵守这些规则。

Advanced Hooks (Advanced Hooks)

We have only considered two out of 10 of the hooks React provides!

We have only considered two out of 10 of the hooks React provides!

What’s interesting is that the knowledge of how useState and useEffect works helps you quickly learn the other hooks.

What's interesting is that the knowledge of how useState and useEffect works helps you quickly learn the other hooks.

Curious to learn about those, I have created a hooks cheatsheet with live editable examples.

Curious to learn about those, I have created a hooks cheatsheet with live editable examples.

Why this is important is that you can immediately begin to tinker with real examples that’ll reinforce your knowledge of how hooks work. All of them!

Why this is important is that you can immediately begin to tinker with real examples that'll reinforce your knowledge of how hooks work. All of them!

Remember that learning is reinforced when you actual solve problems and build stuff.

Remember that learning is reinforced when you actual solve problems and build stuff.

What’s more interesting as well is, after you get through the live examples for each of the hooks, there’s an extra section for other generic examples that don’t exactly fit one hook or require a separate case study.

What's more interesting as well is, after you get through the live examples for each of the hooks, there's an extra section for other generic examples that don't exactly fit one hook or require a separate case study.

In the example section you’ll find examples such as fetching data from a remote server using hooks and more.

In the example section you'll find examples such as fetching data from a remote server using hooks and more.

Go, check it out.

Go, check it out .

Chapter 8: Advanced React Patterns with Hooks (Chapter 8: Advanced React Patterns with Hooks)

With the release of hooks, certain React patterns have gone out of favour. They can still used, but for most use cases you’re likely better off using hooks. For example, choose hooks over render props or higher order components.

With the release of hooks, certain React patterns have gone out of favour. They can still used, but for most use cases you're likely better off using hooks. For example, choose hooks over render props or higher order components.

There may be specific use cases where these could still be used, but most of the times, choose hooks.

There may be specific use cases where these could still be used, but most of the times, choose hooks.

That being said, we will now consider some more advanced React patterns implemented with hooks.

That being said, we will now consider some more advanced React patterns implemented with hooks.

Ready?

准备?

介绍 (Introduction)

This chapter may be the longest in the book, and for good reason. Hooks are likely the way we’ll be writing React components in the next couple of years, and so they are quite important.

This chapter may be the longest in the book, and for good reason. Hooks are likely the way we'll be writing React components in the next couple of years, and so they are quite important.

In this chapter, we’ll consider the following advanced React patterns:

In this chapter, we'll consider the following advanced React patterns:

  • Compound Components

    Compound Components
  • Props Collection

    Props Collection
  • Prop Getters

    Prop Getters
  • State Initializers

    State Initializers
  • State Reducer

    State Reducer
  • Control Props

    Control Props

If you’re completely new to these advanced patterns, don’t worry, I’ll explain them in detail. If you’re familiar with how these patterns work from previous experiences with class components, I’ll show you how to use these patterns with hooks.

If you're completely new to these advanced patterns, don't worry, I'll explain them in detail. If you're familiar with how these patterns work from previous experiences with class components, I'll show you how to use these patterns with hooks.

Now, let’s get started.

Now, let's get started.

Why Advanced Patterns? (Why Advanced Patterns?)

John’s had a fairly good career. Today, he’s a senior frontend engineer at ReactCorp. A great startup changing the world for good.

John's had a fairly good career. Today, he's a senior frontend engineer at ReactCorp . A great startup changing the world for good.

ReactCorp is beginning to scale their workforce. A lot of engineers are being hired and John’s beginning to work on building reusable components for the entire team of engineers.

ReactCorp is beginning to scale their workforce. A lot of engineers are being hired and John's beginning to work on building reusable components for the entire team of engineers.

Yes, John can build components with his current React skills, however, with building highly reusable components comes specific problems.

Yes, John can build components with his current React skills, however, with building highly reusable components comes specific problems.

There’s a million different ways the components can be consumed, and you want to give consumers of the component as much flexibility as possible.

There's a million different ways the components can be consumed, and you want to give consumers of the component as much flexibility as possible.

They must be able to extend the functionality and styles of the component as they deem fit.

They must be able to extend the functionality and styles of the component as they deem fit.

The advanced patterns we’ll consider here are tested and tried methods for building very reusable components that don’t cripple flexibility.

The advanced patterns we'll consider here are tested and tried methods for building very reusable components that don't cripple flexibility.

I didn’t create these advanced patterns. Truth be told, most of the advanced React patterns were made popular by one guy, Kent Dodds — an amazing Javascript engineer.

I didn't create these advanced patterns. Truth be told, most of the advanced React patterns were made popular by one guy, Kent Dodds — an amazing Javascript engineer.

The community has received these patterns extremely well, and I’m here to help you understand how they work!

The community has received these patterns extremely well, and I'm here to help you understand how they work!

Compound Components Pattern (Compound Components Pattern)

The first pattern we will consider is called the Compound Components pattern. I know it sounds fancy, so I’ll explain what it really means.

The first pattern we will consider is called the Compound Components pattern. I know it sounds fancy, so I'll explain what it really means.

The keyword in the pattern name is the word Compound.

The keyword in the pattern name is the word Compound .

Literarily, the word compound refers to something that is composed of two or more separate elements.

Literarily, the word compound refers to something that is composed of two or more separate elements.

With respect to React components, this could mean a component that is composed of two or more separate components.

With respect to React components, this could mean a component that is composed of two or more separate components.

It doesn’t end there.

它并没有就此结束。

Any React component can be composed of 2 or more separate components. So, that’s really not a brilliant way to describe compound components.

Any React component can be composed of 2 or more separate components. So, that's really not a brilliant way to describe compound components.

With compound components, there’s more. The separate components within which the main component is composed cannot really be used without the parent.

With compound components, there's more. The separate components within which the main component is composed cannot really be used without the parent.

The main component is usually called the parent, and the separate composed components, children.

The main component is usually called the parent, and the separate composed components, children.

The classic example is to consider the html select element.

The classic example is to consider the html select element.

<select>  <option value="value0">key0</option>  <option value="value1">key1</option>  <option value="value2">key2</option></select>

With select being the parent, and the many option elements, children.

With select being the parent, and the many option elements, children.

This works like a compound component. For one, it really makes no sense to use the <option>key0<;/option> element without a select parent tag. The overall behaviour of a select element also relies on having these composed option elements as well.

This works like a compound component. For one, it really makes no sense to use the <option>key0< ;/option> elemen t with out a select parent tag. The overall beh aviour of a select element also relies on having the se com posed option elements as well.

They are so connected to one another.

They are so connected to one another.

Also, the state of the entire component is managed by select with all child elements dependent on that state.

Also, the state of the entire component is managed by select with all child elements dependent on that state.

Do you get a sense for what compound components are now?

Do you get a sense for what compound components are now?

It is also worth mentioning that compound components are just one of many ways to express the API for your components.

It is also worth mentioning that compound components are just one of many ways to express the API for your components.

For example, while it doesn’t look as good, the select element could have been designed to work like this:

For example, while it doesn't look as good, the select element could have been designed to work like this:

<select options="key:value;anotherKey:anotherValue"><;/select>

This is definitely not the best way to express this API. It make passing attributes to the child components almost impossible.

This is definitely not the best way to express this API. It make passing attributes to the child components almost impossible.

With that in mind, let’s take a look at an example that’ll help you understand and build your own compound components.

With that in mind, let's take a look at an example that'll help you understand and build your own compound components.

Example: Building an Expandable Component. (Example: Building an Expandable Component.)

We’ll be building an Expandable component. Did you ask what that means?

We'll be building an Expandable component. Did you ask what that means?

Well, consider an Expandable component to be a miniature accordion element. It has a clickable header, which toggles the display of an associated body of content.

Well, consider an Expandable component to be a miniature accordion element. It has a clickable header, which toggles the display of an associated body of content.

In the unexpanded state the component would look like this:

In the unexpanded state the component would look like this:

And this, when expanded:

And this, when expanded:

You get the idea, right?

You get the idea, right?

Designing the API (Designing the API)

It’s usually a good idea to write out what the exposed API of your component would look like before building it out.

It's usually a good idea to write out what the exposed API of your component would look like before building it out.

In this case, here’s what we’re going for:

In this case, here's what we're going for:

<Expandable>	<Expandable.Header> Header </Expandable.Header> 	<Expandable.Icon/>    <Expandable.Body> This is the content &lt;/Expandable.Body></Expandable>

In the code block above you’d have noticed I have expressions like this: Expandable.Header

In the code block above you'd have noticed I have expressions like this: Expandable.Header

You could as well do this:

You could as well do this:

<Expandable>	<Header> Header </Expandable.Header> 	<Icon/>    <Body> This is the content </Body></Expandable>

It doesn’t matter. I have chosen Expandable.Header over Header as a matter of personal preference. I find that it communicates dependency on the parent component well, but that’s just my preference. A lot of people don’t share the same preference and that’s perfectly fine.

没关系 I have chosen Expandable.Header over Header as a matter of personal preference. I find that it communicates dependency on the parent component well, but that's just my preference. A lot of people don't share the same preference and that's perfectly fine.

It’s your component, use whatever API looks good to you :)

It's your component, use whatever API looks good to you :)

Building the Expandable Component (Building the Expandable Component)

The Expandable component being the parent component will keep track of state, and It will do this via a boolean variable called expanded.

The Expandable component being the parent component will keep track of state, and It will do this via a boolean variable called expanded .

// state {  expanded: true || false}

The Expandable component needs to communicate the state to every child component regardless of their position in the nested component tree.

The Expandable component needs to communicate the state to every child component regardless of their position in the nested component tree.

Remember that the children are dependent on the parent compound component for state.

Remember that the children are dependent on the parent compound component for state.

How best may we go about this?

How best may we go about this?

If you said context, you’re correct!

If you said context , you're correct!

We need to create a context object to hold the component state, and expose the expanded property via the Provider component. Alongside the expanded property, we will also expose a function callback to toggle the value of this expanded state property.

We need to create a context object to hold the component state, and expose the expanded property via the Provider component. Alongside the expanded property, we will also expose a function callback to toggle the value of this expanded state property.

If that sounds alright to you, here’s the starting point for the Expandable component.

If that sounds alright to you, here's the starting point for the Expandable component.

// Expandable.js import React, { createContext } from 'react'
const ExpandableContext = createContext()const { Provider } = ExpandableContext
const Expandable = ({children}) => {  return <Provider>{children}</Provider>}export default Expandable

There’s nothing spectacular going on in the code block above.

There's nothing spectacular going on in the code block above.

A context object is created and the Provider component deconstructed. Then we go on to create the Expandable component which renders the Provider and any children.

A context object is created and the Provider component deconstructed. Then we go on to create the Expandable component which renders the Provider and any children .

Got that?

了解?

With the basic setup out of the way, let’s do a little more.

With the basic setup out of the way, let's do a little more.

The context object was created with no initial value. However, we need the Provider to expose the state value expanded and a toggle function to update the state.

The context object was created with no initial value. However, we need the Provider to expose the state value expanded and a toggle function to update the state.

Let’s create the expanded state value using useState.

Let's create the expanded state value using useState .

// Expandable.js
import React, { createContext, useState } from 'react'...const Expandable = ({children}) => {  // look here ?  const [expanded, setExpanded] = useState(false)
return <Provider>{children}</Provider>}

With the expanded state variable created, let’s create the toggle updater function to toggle the value of expanded — whether true or false.

With the expanded state variable created, let's create the toggle updater function to toggle the value of expanded — whether true or false .

// Expandable.js ...const Expandable = ({children}) => {  const [expanded, setExpanded] = useState(false)  // look here ?  const toggle = setExpanded(prevExpanded => !prevExpanded)
return <Provider>{children}</Provider>}

The toggle function invokes setExpanded, the actual updater function returned from the useState call.

The toggle function invokes setExpanded , the actual updater function returned from the useState call.

Every updater function from the useState call can receive a function argument. This is similar to how you pass a function to setState e.g. setState(prevState => !prevState.value).

Every updater function from the useState call can receive a function argument. This is similar to how you pass a function to setState eg setState(prevState => !prevState.val ue).

This is the same thing I’ve done above. The function passed to setExpanded receives the previous value of expanded and returns the opposite of that, !expanded

This is the same thing I've done above. The function passed to setExpanded receives the previous value of expanded and returns the opposite of that, !expanded

toggle acts as a callback function and It’ll eventually be invoked by Expandable.Header. Let’s prevent any future performance issue by memoizing the callback.

toggle acts as a callback function and It'll eventually be invoked by Expandable.Header . Let's prevent any future performance issue by memoizing the callback.

... import { useCallback } from 'react';
const Expandable = ({children}) => {  const [expanded, setExpanded] = useState(false)  // look here ?  const toggle = useCallback(    () => setExpanded(prevExpanded => !prevExpanded),    []  ))return <Provider>{children}</Provider>

Not sure how useCallback works? You probably skipped the previous advanced hooks section that pointed to the cheatsheet. Have a look.

Not sure how useCallback works? You probably skipped the previous advanced hooks section that pointed to the cheatsheet. Have a look .

Once we have both expanded and toggle created, we can expose these via the Provider’s value prop.

Once we have both expanded and toggle created, we can expose these via the Provider 's value prop.

...const Expandable = ({children}) => {  const [expanded, setExpanded] = useState(false)  const toggle = useCallback(    () => setExpanded(prevExpanded => !prevExpanded),    []  )   // look here ?  const value = { expanded, toggle }   // and here ?  return <;Provider value={value}>{children}</Provider>}

This works, but the value reference will be different on every re-render causing the Provider to re-render its children.

This works, but the value reference will be different on every re-render causing the Provider to re-render its children.

Let’s memoize the value.

Let's memoize the value .

...const Expandable = ({children}) => {  ... // look here ?  const value = useMemo(	() => ({ expanded, toggle }), 	[expanded, toggle]  )  return <Provider value={value}>{children}&lt;/Provider>}

useMemo takes a callback that returns the object value { expanded, toggle } and we pass an array dependency [expanded, toggle] so that the memoized value remains the same unless those change.

useMemo takes a callback that returns the object value { expanded, toggle } and we pass an array dependency [expanded, toggle] so that the memoized value remains the same unless those change.

We’ve done a great job so far!

We've done a great job so far!

Now, there’s just one other thing to do on the Expandable parent component.

Now, there's just one other thing to do on the Expandable parent component.

If you remember from a previous experience with class components, it’s possible to do this:

If you remember from a previous experience with class components, it's possible to do this:

this.setState({  name: "value"}, () => {  this.props.onStateChange(this.state.name)})

This is how you trigger a callback after a state change in class components.

This is how you trigger a callback after a state change in class components.

Usually, the callback e.g. this.props.onStateChange is always invoked with the current value of the updated state as shown below:

Usually, the callback eg this.props.onStateChange is always invoked with the current value of the updated state as shown below:

this.props.onStateChange(this.state.name)

Why is this important?

Why is this important?

This is good practice when creating reusable components, because this way the consumer of your component can attach any custom logic to be run after a state update.

This is good practice when creating reusable components, because this way the consumer of your component can attach any custom logic to be run after a state update.

For example:

例如:

const doSomethingPersonal = ({expanded}) => {  // do something really important after being expanded}
<Expandable onExpanded={doSomethingPersonal}> ... </Expandable>

We will add this functionality to the Expanded component.

We will add this functionality to the Expanded component.

With class components this is pretty much straightforward. With functional components, we need to do a little more work — not so much :)

With class components this is pretty much straightforward. With functional components, we need to do a little more work — not so much :)

Whenever you want to perform a side effect within a functional component, for most cases, always reach out for useEffect.

Whenever you want to perform a side effect within a functional component, for most cases, always reach out for useEffect .

So, the easiest solution might look like this:

So, the easiest solution might look like this:

useEffect(() => {  props.onExpanded(expanded)}, [expanded])

The problem however with this is that the useEffect effect function is called at least once — when the component is initially mounted.

The problem however with this is that the useEffect effect function is called at least once — when the component is initially mounted.

So, even though there’s a dependency array, [expanded], the callback will also be invoked when the component mounts!

So, even though there's a dependency array, [expanded] , the callback will also be invoked when the component mounts!

useEffect(() => {  // this function will always be invoked on mount})

The functionality we seek requires that the callback to be passed by the user isn’t invoked on mount.

The functionality we seek requires that the callback to be passed by the user isn't invoked on mount.

How can we enforce this?

How can we enforce this?

First, consider the naive solution below:

First, consider the naive solution below:

//faulty solution... let componentJustMounted = trueuseEffect(    () => {        if(!componentJustMounted) {        props.onExpand(expanded)        componentJustMounted = false      }    },    [expanded]  )...

What’s wrong with the code above?

What's wrong with the code above?

Loosely speaking, the thinking behind the code is correct. You keep track of a certain variable componentJustMounted and set it to true, and only call the user callback onExpand when componentJustMounted is false.

Loosely speaking, the thinking behind the code is correct. You keep track of a certain variable componentJustMounted and set it to true , and only call the user callback onExpand when componentJustMounted is false.

The componentJustMounted value is only set to false after the user callback has been invoked at least once.

The componentJustMounted value is only set to false after the user callback has been invoked at least once.

Looks good.

看起来挺好的。

However, the problem with this is that whenever the function component re-renders owing to a state or prop change, the componentJustMounted value will always be reset to true. Thus, the user callback onExpand will never be invoked as it is only invoked when componentJustMounted is falsey.

However, the problem with this is that whenever the function component re-renders owing to a state or prop change, the componentJustMounted value will always be reset to true . Thus, the user callback onExpand will never be invoked as it is only invoked when componentJustMounted is falsey.

...if (!componentJustMounted) {    	onExpand(expanded)}...

Well, the solution to this is simple. We can use the useRef hook to ensure that a value stays the same all through lifetime of the component.

Well, the solution to this is simple. We can use the useRef hook to ensure that a value stays the same all through lifetime of the component.

Here’s how it works:

运作方式如下:

//correct implementation  const componentJustMounted = useRef(true)  useEffect(    () => {      if (!componentJustMounted.current) {        onExpand(expanded)      }      componentJustMounted.current = false    },    [expanded]  )

useRef returns a ref object and the value stored in the object may be retrieved from ref.current

useRef returns a ref object and the value stored in the object may be retrieved from ref.current

The signature for useRef looks like this: useRef(initialValue).

The signature for useRef looks like this: useRef(initialValue) .

Hence, stored initially in componentJustMounted.current is a ref object with the current property set to true.

Hence, stored initially in componentJustMounted.current is a ref object with the current property set to true .

const componentJustMounted = useRef(true)

After invoking the user callback, we then update this value to false.

After invoking the user callback, we then update this value to false .

componentJustMounted.current = false

Now, whenever there’s a state or prop change the value in the ref object isn’t tampered with. It remains the same.

Now, whenever there's a state or prop change the value in the ref object isn't tampered with. It remains the same.

With the current implementation, whenever the expanded state value is toggled, the user callback function onExpanded will be invoked with the current value of expanded.

With the current implementation, whenever the expanded state value is toggled, the user callback function onExpanded will be invoked with the current value of expanded .

Here’s what the final implementation of the Expandable component now looks like:

Here's what the final implementation of the Expandable component now looks like:

// Expandable.js const Expandable = ({ children, onExpand }) => {  const [expanded, setExpanded] = useState(false)  const toggle = useCallback(    () => setExpanded(prevExpanded => !prevExpanded),    []  )  const componentJustMounted = useRef(true)  useEffect(    () => {      if (!componentJustMounted) {        onExpand(expanded)      }       componentJustMounted.current = false    },    [expanded]  )  const value = useMemo(   () => ({ expanded, toggle }),    [expanded, toggle]  )  return (    <Provider value={value}>        {children}    </Provider>  )}

If you’ve followed along, that’s great. We’ve sorted out the most complex component in the bunch. Now, let’s build the child components.

If you've followed along, that's great. We've sorted out the most complex component in the bunch. Now, let's build the child components.

Building the Compound Child Components (Building the Compound Child Components)

There are three child components for the Expandable component.

There are three child components for the Expandable component.

These child components need to consume values from the context object created in Expandable.js.

These child components need to consume values from the context object created in Expandable.js .

To make this possible, we’ll do a little refactoring as shown below:

To make this possible, we'll do a little refactoring as shown below:

export const ExpandableContext = createContext()

We export the context object, ExpandableContext from Expandable.js.

We export the context object, ExpandableContext from Expandable.js .

Now, we may use the useContext hook to consume the values from the Provider.

Now, we may use the useContext hook to consume the values from the Provider .

Below’s the Header child component fully implemented.

Below's the Header child component fully implemented.

//Header.jsimport React, { useContext } from 'react'import { ExpandableContext } from './Expandable'
const Header = ({children}) => {  const { toggle } = useContext(ExpandableContext)  return <div onClick={toggle}>{children}</div>}export default Header

Simple, huh?

简单吧?

It renders a div whose onClick callback is the toggle function for toggling the expanded state within the Expandable parent component.

It renders a div whose onClick callback is the toggle function for toggling the expanded state within the Expandable parent component.

Here’s the implementation for the Body child component:

Here's the implementation for the Body child component:

// Body.jsimport { useContext } from 'react'import { ExpandableContext } from './Expandable'
const Body = ({ children }) => {  const { expanded } = useContext(ExpandableContext)  return expanded ? children : null}export default Body

Pretty simple as well.

Pretty simple as well.

The expanded value is retrieved from the context object and used within the rendered markup. It reads like this: If expanded, render children else render nothing.

The expanded value is retrieved from the context object and used within the rendered markup. It reads like this: If expanded, render children else render nothing.

The Icon component is just as simple.

The Icon component is just as simple.

// Icon.jsimport { useContext } from 'react'import { ExpandableContext } from './Expandable'
const Icon = () => {  const { expanded } = useContext(ExpandableContext)  return expanded ? '-' : '+'}export default Icon

It renders either + or - depending on the value of expanded retrieved from the context object.

It renders either + or - depending on the value of expanded retrieved from the context object.

With all child components built, we can set the child components as Expandable properties. See below:

With all child components built, we can set the child components as Expandable properties. 见下文:

import Header from './Header'import Icon from './Icon'import Body from './Body'
const Expandable = ({ children, onExpand }) => {	...}
// Remember this is just a personal reference. It's not mandatoryExpandable.Header = HeaderExpandable.Body = BodyExpandable.Icon = Icon

Now we can go ahead to use the Expandable component as designed:

Now we can go ahead to use the Expandable component as designed:

<Expandable>    <Expandable.Header>React hooks</Expandable.Header>           <Expandable.Icon />    <Expandable.Body>Hooks are awesome&lt;/Expandable.Body></Expandable>

Does it work?

Does it work?

You bet!

You bet!

Here’s what’s rendered when not expanded:

Here's what's rendered when not expanded:

And when expanded:

And when expanded:

This works but it has to be the ugliest component I’ve ever seen. We can do better.

This works but it has to be the ugliest component I've ever seen. 我们可以做得更好。

Manageable Styling for Reusable Components (Manageable Styling for Reusable Components)

Hate it or not, styling (or CSS) is integral to how the web works.

Hate it or not, styling (or CSS) is integral to how the web works.

While there’s a number of ways to style a React component, and I’m sure you have a favourite, when you build reusable components it’s always a good idea to expose a frictionless API for overriding default styles.

While there's a number of ways to style a React component, and I'm sure you have a favourite, when you build reusable components it's always a good idea to expose a frictionless API for overriding default styles.

Usually, I recommend letting it possible to have your components styleable via both style and className props.

Usually, I recommend letting it possible to have your components styleable via both style and className props.

For example:

例如:

// this should work.<MyComponent style={{name: "value"}} />// and this.<MyComponent className="my-class-name-with-dope-styles" />

Now, our goal isn’t just styling the component, but to make it as reusable as possible. This means letting whoever consumes the component style the component whichever they want i.e inline style via the style prop, or by passing some className prop.

Now, our goal isn't just styling the component, but to make it as reusable as possible. This means letting whoever consumes the component style the component whichever they want ie inline style via the style prop, or by passing some className prop.

Let’s begin with the Header child component:

Let's begin with the Header child component:

// before const Header = ({children}) => {  const { toggle } = useContext(ExpandableContext)  return <div onClick={toggle}>{children}</div>}

First, let’s change the rendered markup to a button. It’s a more accessible and semantic alternative to the div used earlier.

First, let's change the rendered markup to a button . It's a more accessible and semantic alternative to the div used earlier.

const Header = ({children}) => {  const { toggle } = useContext(ExpandableContext)  // look here ?  return <button onClick={toggle}>{children}<;/button>}

We will now write some default styles for the Header component in a Header.css file.

We will now write some default styles for the Header component in a Header.css file.

// Header.css.Expandable-trigger {    background: none;    color: hsl(0, 0%, 13%);    display: block;    font-size: 1rem;    font-weight: normal;    margin: 0;    padding: 1em 1.5em;    position: relative;    text-align: left;    width: 100%;    outline: none;    text-align: center;  }    .Expandable-trigger:focus,  .Expandable-trigger:hover {    background: hsl(216, 94%, 94%);  }

I’m sure you can figure out the simple CSS above. If not, don’t stress it. What’s important is to note the default className used here, .Expandable-trigger

I'm sure you can figure out the simple CSS above. If not, don't stress it. What's important is to note the default className used here, .Expandable-trigger

To apply these styles, we need to import the CSS file and apply the appropriate className prop to the rendered button.

To apply these styles, we need to import the CSS file and apply the appropriate className prop to the rendered button .

... import './Header.css'const Header = () => {  const { toggle } = useContext(ExpandableContext)  return <button onClick={toggle} 	 className="Expandable-trigger">	{children}&lt;/button>}

This works great, however the className is set to the default string Expandable-trigger.

This works great, however the className is set to the default string Expandable-trigger .

This will apply the styling we’ve written in the CSS file, but it doesn’t take into the account any className prop passed in by the user.

This will apply the styling we've written in the CSS file, but it doesn't take into the account any className prop passed in by the user.

It’s important to accommodate passing this className prop as a user might like to change the default style you’ve set in your CSS.

It's important to accommodate passing this className prop as a user might like to change the default style you've set in your CSS .

Here’s one way to do this:

Here's one way to do this:

// Header.jsimport './Header.css'const Header = ({ children, className}) => {  // look here ?  const combinedClassName = `Expandable-trigger ${className}`  return (    <button onClick={toggle} className={combinedClassName}>      {children}    </button>  )}

Now, whatever className is passed to the Header component will be combined with the default Expandable-trigger before been passed on to the rendered button element.

Now, whatever className is passed to the Header component will be combined with the default Expandable-trigger before been passed on to the rendered button element.

Let’s consider how good the current solution is.

Let's consider how good the current solution is.

First, if the className prop is null or undefined, the combinedClassName variable will hold the value "Expandable-trigger null" or "Expandable-trigger undefined".

First, if the className prop is null or undefined , the combinedClassName variable will hold the value "Expandable-trigger null" or "Expandable-trigger undefined".

To prevent this, be sure to pass a className by using the ES6 default parameters syntax as shown below:

To prevent this, be sure to pass a className by using the ES6 default parameters syntax as shown below:

// note how className defaults to an empty stringconst Header = ({ children, className = '' }) => {  ...}

Having provided a default value, if the user still doesn’t enter a className, the combinedClassName value will be equal to "Expandable-trigger ".

Having provided a default value, if the user still doesn't enter a className , the combinedClassName value will be equal to "Expandable-trigger " .

Note the empty string appended to the Expandable-trigger. This is owing to how template literals work.

Note the empty string appended to the Expandable-trigger . This is owing to how template literals work.

My preferred solution is to do this:

My preferred solution is to do this:

const combinedClassName = ['Expandable-trigger', className].join('')

This solution handles the previously discussed edge cases. If you also want to explicit about removing null, undefined or any other falsey values, you can do the following:

This solution handles the previously discussed edge cases. If you also want to explicit about removing null , undefined or any other falsey values, you can do the following:

const combinedClassName = ['Expandable-trigger', className].filter(Boolean).join('')

I’ll stick with the simpler alternative, and providing a default for className via default parameters.

I'll stick with the simpler alternative, and providing a default for className via default parameters.

With that being said, here’s the final implementation for Header:

With that being said, here's the final implementation for Header :

// after ...import './Header.css'const Header = ({ children, className = ''}) => {  const { toggle } = useContext(ExpandableContext)  const combinedClassName = ['Expandable-trigger', className].join('')
return (    <button onClick={toggle} className={combinedClassName}>      {children}    </button>  )}

So far, so good.

到目前为止,一切都很好。

Incase you were wondering, combinedClassName returns a string. Since strings are compared by value, there’s no need to memoize this value with useMemo.

Incase you were wondering, combinedClassName returns a string. Since strings are compared by value, there's no need to memoize this value with useMemo .

So far, we’ve graciously handled the className prop. How about the option to override default styles by passing a style prop?

So far, we've graciously handled the className prop. How about the option to override default styles by passing a style prop?

Well, let’s fix that.

Well, let's fix that.

Instead of explicitly destructuring the style prop, we can pass on any other prop passed by the user to the button component.

Instead of explicitly destructuring the style prop, we can pass on any other prop passed by the user to the button component.

// rest paramter ...otherProps ?const Header = ({ children, className = '', ...otherProps }) => {	return (    // spread syntax {...otherProps} ?    <button {...otherProps}>      {children}    </button>  )}

Note the use of the rest parameter and spread syntax.

Note the use of the rest parameter and spread syntax .

With this done, the Header component receives our default styles, yet allows for change via the className or style props.

With this done, the Header component receives our default styles, yet allows for change via the className or style props.

// override style via className<Expandable.Header className="my-class">	React hooks</Expandable.Header>
// override style via style prop<Expandable.Header style={{color: "red"}}>	React hooks</Expandable.Header>

Now, I’ll go ahead and do the same for the other child components, Body and Icon.

Now, I'll go ahead and do the same for the other child components, Body and Icon .

// before const Body = ({ children }) => {  const { expanded } = useContext(ExpandableContext)  return expanded ? children : null}
// after import './Body.css'const Body = ({ children, className = '', ...otherProps }) => {  const { expanded } = useContext(ExpandableContext)  const combinedClassNames = ['Expandable-panel', className].join('')
return expanded ? (    <div className={combinedClassNames} {...otherProps}>      {children}    </div>  ) : null}
// Body.css.Expandable-panel {    margin: 0;    padding: 1em 1.5em;    border: 1px solid hsl(216, 94%, 94%);;    min-height: 150px;  }

Do the same for Icon component:

Do the same for Icon component:

// before const Icon = () => {  const { expanded } = useContext(ExpandableContext)  return expanded ? '-' : '+'}
// after ...import './Icon.css'const Icon = ({ className = '', ...otherProps }) => {  ...  const combinedClassNames = ['Expandable-icon', className].join('')
return (    <span className={combinedClassNames} {...otherProps}>      {expanded ? '-' : '+'}    </span>  )}
// Icon.css.Expandable-icon {    position: absolute;    top: 16px;    right: 10px;}

And finally, some styles for the parent component, Expandable.

And finally, some styles for the parent component, Expandable .

import './Expandable.css'const Expandable = ({ children, onExpand, className = '', ...otherProps }) => {   ...   const combinedClassNames = ['Expandable', className].join('')  return (    <Provider value={value}>      <div className={combinedClassNames} {...otherProps}>        {children}      </div>    </Provider>  )}
// Expandable.css.Expandable {     position: relative;     width: 350px;}

Now we’ve got a beautiful reusable component!

Now we've got a beautiful reusable component!

We’ve not just made it beautiful, but it’s customisable as well.

We've not just made it beautiful, but it's customisable as well.

How customisable is the component we’ve built?

How customisable is the component we've built?

See what I’ve done below with the same component!

See what I've done below with the same component!

And this didn’t take a lot of code.

And this didn't take a lot of code.

<Expandable>    <Expandable.Header>Reintroducing React</Expandable.Header>    <Expandable.Icon />    <Expandable.Body>     	<img            src='https://i.imgur.com/qpj4Y7N.png'            style={{ width: '250px' }}            alt='reintroducing react book cover'        />        <p style={{ opacity: 0.7 }}>          This book is so f*cking amazing! <br />        <a          href='https://leanpub.com/reintroducing-react'          target='_blank'          rel='noopener noreferrer'          >            Go get it now.        </a>       </p>     </Expandable.Body></Expandable>

You can go one step further to test if overriding styles via the style prop works as well.

You can go one step further to test if overriding styles via the style prop works as well.

<Expandable>   <Expandable.Header       // look here ?	  style={{ color: 'red', border: '1px solid teal' }}>        Reintroducing React    </Expandable.Header>        ...</Expandable>

And below’s the result of that:

And below's the result of that:

Yay! it works as expected.

好极了! it works as expected.

Note: I have covered 5 other advanced component patterns with Hooks in the ebook (PDF, Epub and Mobi). You can get it completely free (or pay whatever you want if you like my work).

Note : I have covered 5 other advanced component patterns with Hooks in the ebook (PDF, Epub and Mobi). You can get it completely free (or pay whatever you want if you like my work).

结论 (Conclusion)

This has been a lengthy discourse on the modern changes in React. If you don’t get all of it yet, spend a little more time practising the examples in your day to day work, and I’m pretty sure you’ll get a hang of it real quick.

This has been a lengthy discourse on the modern changes in React. If you don't get all of it yet, spend a little more time practising the examples in your day to day work, and I'm pretty sure you'll get a hang of it real quick.

When you do, go be the React engineer with a decent understanding of Modern React and go build highly reusable components with advanced hook patterns.

When you do, go be the React engineer with a decent understanding of Modern React and go build highly reusable components with advanced hook patterns.

Thank you for following me on this journey. Got questions? Use the comment section!

Thank you for following me on this journey. 有问题吗? Use the comment section!

翻译自: https://www.freecodecamp.org/news/reintroducing-react-every-react-update-since-v16-demystified-60686ee292cc/

react引入多个图片

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值