组件和高阶组件区别_高阶组件:终极指南

本文深入探讨了在React应用中如何使用高阶组件(HoC)提高代码的可维护性和可重用性。通过遵循函数式编程的原则,如使用高阶函数、避免副作用、注重组件的组成,可以将复杂的组件拆分为可复用的简单功能。HoC允许我们将逻辑从表现组件中移除,使组件更专注于UI渲染,从而简化代码并提高可测试性。文章通过实例展示了如何使用HoC改进组件结构,并提到了Recompose库在组合和优化HoC方面的作用。
摘要由CSDN通过智能技术生成

组件和高阶组件区别

The maintainable component structure is a crucial prerequisite for a stable React application. You can achieve this by writing your code in a functional way using higher-order components (HoCs). If you stick to this pattern, you’ll end up with reusable components that are both readable and easy to test as each component is only responsible for a single task.

可维护的组件结构是稳定React应用程序的关键先决条件。 您可以通过使用高阶组件(HoC)以功能性的方式编写代码来实现此目的。 如果坚持这种模式,您将最终获得可读性强且易于测试的可重用组件,因为每个组件仅负责一项任务。

In this article I’d love to share my experience, so you can easily utilize this approach in your own applications. Not only will you learn how to enhance your presentational components using one or several HoCs, but you’ll also understand the principles behind this pattern.

在本文中,我很想分享我的经验,因此您可以轻松地在自己的应用程序中使用这种方法。 您不仅将学习如何使用一个或多个HoC来增强演示组件,而且还将了解该模式背后的原理。

为什么这篇文章这么长? (Why is this post so long?)

When I started learning HoCs myself, I had no problem finding resources dealing with this topic. However, many of them assumed certain previous knowledge of complex topics, such as functional programming (FP) principles. As a result, it was challenging for me to understand what was going on under the hood and how the composition of several HoCs works.

当我自己开始学习HoC时,我没有遇到问题的资源。 但是,他们中的许多人都具有复杂主题的某些先前知识,例如函数式编程(FP)原理。 结果,对我而言,要了解幕后的情况以及几个HoC的组成如何工作是一个挑战。

It was this experience that motivated me to write this article in a broader and more beginner-friendly way. So, it covers not only HoCs, but also the principles of FP and the core ideas that one must understand to be able to unleash the power of higher-order components.

正是这种经历促使我以更广泛,更适合初学者的方式撰写本文。 因此,它不仅涵盖了HoC,而且还涵盖了FP的原理以及人们必须理解的才能释放出高阶组件的力量的核心思想。

This article is also based on my first tech conference talk I gave at the International JavaScript Conference (iJS) 2017 in Munich. You can find all the source code on Github.

本文还基于在慕尼黑的2017年国际JavaScript大会(iJS)上进行的第一次技术会议演讲 。 您可以在Github上找到所有源代码。

入门 (Getting started)

Let’s get started by looking at some code:

让我们开始看一些代码:

const starWarsChars = [   { name:'Luke', side:'light' },   { name:'Darth Vader', side:'dark' },   { name:'Obi-wan Kenobi', side:'light'},   { name:'Palpatine', side:'dark'},]
class FilteredList extends React.Component {   constructor(props) {      super(props)      this.state = { value: this.props.defaultState }   }   updateState(value) {      this.setState({ value })   }   render() {      const otherSide = this.state.value === 'dark' ? 'light' : 'dark'      const transformedProps = this.props.list.filter(char =&gt; char.side === this.state.value)      return (         <div>            <button onClick={() => this.updateState(otherSide)}>Switch</button>            {transformedProps.map(char =&gt;               <div key={char.name}>                  <div>Character: {char.name}</div>                  <div>Side: {char.side}</div>               </div>            )}         </div>      )   }}
ReactDOM.render (   <FilteredList defaultState='dark' list={starWarsChars} />,   document.getElementById('app'))

FilteredList is a huge component that does so many things. It maintains the state and filters the list of the Star Wars characters according to their side. Moreover, it renders the character list with a button to the screen.

FilteredList是一个巨大的组件,它可以完成很多事情。 它维护状态并根据角色的侧面过滤“星球大战”角色的list 。 而且,它通过屏幕上的按钮呈现字符列表。

It takes care of all the logic and presentation, and because of that, it’s hardly ever reusable.

它照顾了所有逻辑和表示,因此,它几乎无法重用。

If you decide to reuse this component elsewhere, you’ll always need to use all the component’s logic and UI. You can’t just cherry pick the functionality you really need for a particular scenario. Instead, you’ll be forced to rewrite an already existing piece of behavior as a different component.

如果您决定在其他地方重用此组件,则始终需要使用所有组件的逻辑和UI。 您不能仅仅选择特定情况下真正需要的功能。 相反,您将被迫将已经存在的行为改写为其他组件。

As a result, such repeated code would be difficult to maintain, especially in a larger application.

结果,这种重复的代码将难以维护,尤其是在较大的应用程序中。

At the end of this article, we’ll be able to write a fully reusable version of this code using the principles of functional programming (FP).

在本文的最后,我们将能够使用函数式编程(FP)的原理编写该代码的完全可重用版本。

Stay tuned.

敬请关注。

品尝函数式编程的原理 (Taste the principles of functional programming)

To show you why you should stick to the principles of FP in a React application, I need to talk a little bit about the core principles of FP themselves.

为了向您展示为什么您应该在React应用程序中坚持FP的原理,我需要谈谈FP本身的核心原理。

The idea is to decompose a program into simple reusable functions.

这个想法是将程序分解为简单的可重用函数

So, it’s all about functions. To be more precise, it’s all about simple functions. This means that each function should only be responsible for a single task. The simpler the function, the more reusable it is.

因此,这全都与功能有关。 更确切地说,这都是关于简单函数的 。 这意味着每个功能应仅负责单个任务。 功能越简单,可重用性越高。

高阶函数 (Higher-order functions)

In JavaScript, you can use a function like any other value. It can be passed as an argument to a function or it can be returned by it. A function that returns or creates a new function is called a higher-order function.

在JavaScript中,您可以像使用其他任何值一样使用函数。 它可以作为参数传递给函数,也可以由它返回。 返回或创建新函数的函数称为高阶函数。

const numbers = [1, 5, 8, 10, 21]const createAddingFunction = number =&gt; arr => arr.map(num =&gt; num + number)const numbersPlusOne = createAddingFunction(1)console.log(numbersPlusOne(numbers))  // [2, 6, 9, 11, 22]

createAddingFunctions is a higher-order function. It takes a number and creates a new function waiting for the array to be passed. In the example, we pass it 1 and get back a new function waiting for an array. We store it as numbersPlusOne. Then we pass the numbers array to it. The function then iterates over the array’s elements and increases each by one.

createAddingFunctions是一个高阶函数。 它需要一个number并创建一个新函数,等待数组通过。 在示例中,我们将其传递1并返回一个等待数组的新函数。 我们将其存储为numbersPlusOne 。 然后我们将numbers数组传递给它。 然后,该函数迭代数组的元素,并将每个元素加1。

As you can see, we’re telling the JavaScript engine what we want to do — we want to map over the array’s elements. This code is self-explanatory. You just see the code and you immediately know what’s going on. Such code is called declarative. Functional programming is all about declarative code.

正如你所看到的,我们告诉JavaScript引擎我们想做的事-我们要映射在数组中的元素。 此代码是不言自明的。 您只要看一下代码,就可以立即知道发生了什么。 这样的代码称为声明性代码。 函数式编程全部与声明性代码有关。

避免副作用 (Avoid side effects)

As a functional programmer, you want to avoid side effects in your functions as much as possible. In other words, a function shouldn’t be changing anything that’s not local to the function itself. You can reuse such a function easily, anywhere in your application. Functions without side effects are called pure. They always return the same output, given the same arguments.

作为函数式程序员,您希望尽可能避免函数中的副作用。 换句话说,函数不应更改不在函数本身本地的任何内容。 您可以在应用程序中的任何位置轻松重用此功能。 没有副作用的功能称为纯功能。 给定相同的参数,它们总是返回相同的输出。

If you want to write pure functions, you should also avoid mutating your values. This is called the principle of immutability. However, this doesn’t mean you don’t change your values. It means that when you want to change a value, you create a new one rather than mutating the original one.

如果要编写纯函数,则还应避免更改值。 这就是所谓的不变性原则。 但是,这并不意味着您不更改值。 这意味着,当您要更改值时,可以创建一个新值,而不是对原始值进行变异。

However, in JavaScript, values such as objects and arrays are mutable. In order to respect the principle of immutability, we can treat the values as immutable.

但是,在JavaScript中,对象和数组等值是可变的。 为了遵守不变性原则,我们可以将值视为不变。

For example, adhering to this principle, you won’t be able to accidentally mutate an object that was passed to a function as its parameter.

例如,秉承这一原则,您将无法意外地更改作为参数传递给函数的对象。

// pure functionconst numbers = [1, 5, 8, 10, 21]const createAddingFunction = number =&gt; arr => arr.map(num =&gt; num + number)const numbersPlusOne = createAddingFunction(1)console.log(numbersPlusOne(numbers))  //[2, 6, 9, 11, 22]console.log(numbers)  // [1, 5, 8, 10, 21]
// impure functionconst numbers = [1, 5, 8, 10, 21]const numbersPlusOne = numbers =&gt; {   for(let i = 0; i < numbers.length; i++) {      numbers[i] = numbers[i] + 1   }   return numbers}numbersPlusOne(numbers) // [2, 6, 9, 11, 22]console.log(numbers) // [2, 6, 9, 11, 22]

Here we have an example of a pure (same as in a previous example) and impure function. In the first case, the fact that we passed an array to the pure function didn’t affect the numbers array in any way.

这里有一个纯函数(与前面的示例相同)和不纯函数的示例。 在第一种情况下,我们将数组传递给纯函数的事实丝毫不影响numbers数组。

However, in the second scenario, the array was mutated inside the impure function. Such behavior can make your code pretty unpredictable. And especially in the functional programming realm, we want to avoid that.

但是,在第二种情况下,数组在不纯函数内部发生了变异。 这样的行为会使您的代码难以预测。 特别是在函数式编程领域,我们希望避免这种情况。

组成 (Composition)

By now, we know we should be creating simple pure functions. However, what if we need behavior that is so complex that it can’t be stored in a single function? We could achieve this by combining several functions into a new compound function using composition.

现在,我们知道应该创建简单的纯函数。 但是,如果我们需要如此复杂的行为以致无法将其存储在单个函数中怎么办? 我们可以通过使用composition将几个函数组合成一个新的复合函数来实现。

const number = 15const increment = num =&gt; num + 5const decrement = num =>; num - 3const multiply = num =&gt; num * 2
const operation = increment(decrement(multiply(number)))console.log(operation)  //32

Composition means that we pass the output of the first function call as the input to the second function call, its output to the third function and so on. As a result, we get a compound function.

组合意味着我们将第一个函数调用的输出作为输入传递给第二个函数调用,将其输出传递给第三个函数,依此类推。 结果,我们得到了一个复合函数。

In our example, we have a number and three functions. We wrap them all inside each other, and we get a compound function waiting for the number argument. By using composition, we don’t need to create variables for storing the result of the single functions.

在我们的示例中,我们有一个number和三个函数。 我们将它们全部包装在一起,然后得到一个等待number参数的复合函数。 通过使用组合,我们不需要创建变量来存储单个函数的结果。

组合式 (Combined)

To really see the benefits of all these FP principles, you need to combine them together.

要真正看到所有这些FP原理的好处,您需要将它们结合在一起。

Ideally, your application should be composed of pure functions whose data are treated as immutable. That means they’re not modifying their upper scope and so you’re free to reuse them in any part of your program. Each function should be responsible for a single task and should be separated from the other ones. You can use them as they are or you can compose them together to achieve more complex behavior.

理想情况下,您的应用程序应由数据被视为不可变纯函数组成 这意味着它们没有修改其上限,因此您可以在程序的任何部分中随意使用它们。 每个功能应负责一项任务,并应与其他任务分开。 您可以按原样使用它们,也可以将它们组合在一起以实现更复杂的行为。

By sticking to FP principles, you’ll end up with simple reusable functions that can be composed together.

坚持FP原则,您将获得可以组合在一起的简单可重用功能。

函数式编程和React (Functional programming and React)

Now that we are familiar with the basic principles of FP, we can take a look at how to use them to our advantage in React.

现在我们已经熟悉了FP的基本原理,接下来我们来看看如何在React中利用它们来发挥我们的优势。

React applications are composed of components. But what exactly is a component?

React应用程序由组件组成。 但是,到底什么是组件?

// Class-based componentclass Button extends React.Component {   render(){      return <button>{this.props.title}</button>   }}
// Functional componentconst Button = (props) =>   <button>{props.title}</button>

Since the class is just syntactic sugar over functions and the functional component is basically a function, components are just functions. It’s a function that takes input data (props) and returns a tree of React elements (UI) which is rendered to the screen. However, it doesn’t need to return UI all the time. It can return a component as well as we’re going to see later.

由于类只是函数的语法糖,而函数组件基本上是函数,因此组件只是函数 。 这个函数接受输入数据(props)并返回呈现到屏幕的React元素树(UI)。 但是,它不需要一直返回UI。 它可以返回一个组件,以及我们以后将要看到的组件。

So React UI is just a composition of functions. That sounds awfully like FP, right?

因此,React UI只是功能组合 。 听起来像FP,对吧?

智能和演示组件 (Smart and presentational components)

A component is typically composed of logic and presentation. However, if we decide to write all our components as such, we would end up with dozens of components having only a single purpose. On the other hand, if we try to separate these concerns, we’ll be able to create simple reusable components. Following this idea, we should prefer defining our components as smart (logic) and presentational (UI).

组件通常由逻辑和表示组成。 但是,如果我们决定以这种方式编写所有组件,那么最终会得到数十个仅具有单一用途的组件。 另一方面,如果我们尝试将这些问题分开 ,我们将能够创建简单的可重用组件。 按照这个想法,我们应该更喜欢将组件定义为智能 (逻辑)和演示 (UI)。

The presentational component takes care of all the UI. It will typically have the form of a functional component, which is just a render method. You can think of them as functions.

呈现组件负责所有UI。 它通常具有功能组件的形式,这只是一个渲染方法。 您可以将它们视为功能。

The component containing mostly logic is called smart. It typically handles data manipulations, API calls, and event handlers. It will often be defined as a class since it provides us with more functionality (such as internal state and lifecycle).

主要包含逻辑的组件称为smart 。 它通常处理数据操作,API调用和事件处理程序。 由于它将为我们提供更多功能(例如内部状态和生命周期),因此通常将其定义为

Each component should be responsible for a single task and written so generally that it can be reused throughout the application. Such a task should be either logic (smart component) or presentation (presentational component). The combination of both in a single component should be minimized.

每个组件都应负责一个任务,并编写成一般性的以便可以在整个应用程序中重用。 这样的任务应该是逻辑(智能组件)或表示(表示组件)。 两者在单个组件中的组合应最小化。

  • smart class component

    智能类组件

class DisplayList extends Component {   constructor(props) {      super(props)      this.state = {         starWarsChars: [            { name:'Luke Skywalker', side:'light' },            { name:'Darth Vader', side:'dark' },            { name:'Obi-wan Kenobi', side:'light' },            { name:'Palpatine', side:'dark' },         ]      }   }   render() {      return (         <div>            {this.state.starWarsChars.map(char =>               <div key={char.name}>                  <div>Character: {char.name}</div>                  <div>Side: {char.side}</div>               </div>            )}         </div>      )   }}
ReactDOM.render(   <DisplayList />,   document.getElementById('app'))
  • presentational functional component

    表达功能成分

const starWarsChars = [   { name:'Luke', side:'light' },   { name:'Darth Vader', side:'dark' },   { name:'Obi-wan Kenobi', side:'light'},   { name:'Palpatine', side:'dark'},]
const DisplayList = ({ list }) =>   <div>      {list.map(char =&gt;         <div key={char.name}>            <div>Character: {char.name}</div>            <div>Side: {char.side}</div>         </div>      )}   </div>
ReactDOM.render (   <DisplayList list={starWarsChars} />,   document.getElementById('app'))

Let’s take a look at the functional component. It’s pretty reusable since it only takes care of the UI. So, if you want to display a list of Star Wars characters elsewhere in your application, you can easily reuse this component. It also doesn’t have any side effects since it doesn’t affect its outer scope in any way.

让我们看一下功能组件。 它非常可重用,因为它只负责UI。 因此,如果要在应用程序的其他位置显示《星球大战》角色的列表,则可以轻松地重用此组件。 它也没有任何副作用,因为它不会以任何方式影响其外部范围。

You can see that the functional component is just a pure function that takes props object and returns the same UI given the same props.

您会看到功能组件只是一个纯函数 ,它接受props对象并在给定相同props的情况下返回相同的UI。

Not only is that React application a composition of functions in general, but it can also be a composition of pure functions.

React应用程序不仅通常是功能的组合,而且还可以是纯功能组合

As we’ve already learned, pure functions are the basic building blocks of FP. So if we prefer using functional components, we’ll be able to apply various FP techniques such as the higher-order components in our code.

正如我们已经了解的那样,纯函数是FP的基本构建块。 因此,如果我们更喜欢使用功能性组件,则可以在代码中应用各种FP技术,例如高阶组件。

添加更多逻辑 (Adding more logic)

Let’s take a look at our functional component again. It takes a list of Star Wars characters as a prop and renders them to the screen. It’s pretty reusable since it doesn’t contain any logic.

让我们再次看一下我们的功能组件。 它以“星球大战”角色列表作为道具并将它们呈现到屏幕上。 由于它不包含任何逻辑,因此可以重用。

Now, what if we wanted to display only characters belonging to the dark side? The simplest solution will be to filter the list prop inside the component.

现在,如果我们只想显示属于暗面的字符怎么办? 最简单的解决方案是在组件内部过滤list属性。

const FilteredList = ({ list, side }) =&gt; {   const filteredList = list.filter(char => char.side === side)   return (      <div>         {filteredList.map(char =&gt;            <div key={char.name}>               <div>Character: {char.name}</div>               <div>Side: {char.side}</div>            </div>         )}      </div>   )}
ReactDOM.render (   <FilteredList side='dark' list={starWarsChars}/>,   document.getElementById('app'))

This will do the trick. We renamed DisplayList to FilteredList since it now contains filtering functionality. We are also now passing the side prop according to which list will be filtered.

这将达到目的。 我们将DisplayList重命名为FilteredList因为它现在包含过滤功能。 我们现在还根据要过滤的列表传递side道具。

However, is this the ideal solution? As you can see, the FilteredList component isn’t reusable anymore. Because of the filter function buried inside of it, this component can hardly ever be reused.

但是,这是理想的解决方案吗? 如您所见, FilteredList组件不再可重用。 由于其内部具有过滤器功能,因此几乎无法重复使用此组件。

If we wanted to display characters elsewhere in our application without any filtering, we would need to create another component. Furthermore, if we wanted to use the filter function in other components, we would have to duplicate this behavior as well.

如果我们想在应用程序中的其他位置显示字符而不进行任何过滤,则需要创建另一个组件。 此外,如果我们想在其他组件中使用过滤器功能,则也必须复制此行为。

Fortunately, there’s a more elegant and declarative solution that lets us keep our presentational component reusable. We are able to filter the characters list before it’s passed as the prop to the DisplayList component.

幸运的是,有一个更优雅,更声明式的解决方案 ,使我们能够保持演示组件的可重用性。 我们可以在将字符列表作为道具传递到DisplayList组件之前对其进行过滤。

const withFilterProps = BaseComponent =&gt; ({ list, side }) => {   const transformedProps = list.filter(char => char.side === side)   return <BaseComponent list={transformedProps} />}
const renderDisplayList = ({ list }) =>   <div>      {list.map(char =&gt;         <div key={char.name}>            <div>Character: {char.name}</div>            <div>Side: {char.side}</div>         </div>      )}   </div>
const FilteredList = withFilterProps(renderDisplayList)
ReactDOM.render (   <FilteredList side='dark' list={starWarsChars} />,   document.getElementById('app'))

We renamed our functional component renderDisplayList to make it obvious that it’s responsible only for the UI rendering.

我们重命名了功能组件renderDisplayList ,以使其明显仅负责UI渲染。

First, let’s take a look at the FilteredList component. This component gets created by passing our functional component renderDisplayList to the withFilterProps higher-order function. When this happens, we get back a functional component and store it as FilteterdList waiting for the props object to be passed.

首先,让我们看一下FilteredList组件。 通过将我们的功能组件renderDisplayList传递给withFilterProps高阶函数来创建此组件。 发生这种情况时,我们取回功能组件并将其存储为FilteterdList等待传递props对象。

We render the FilteredList component at the end of the example by passing the props. It filters the character list from the props according to the side prop. The filtered list is then passed as the props to the renderDisplayList, which subsequently renders the list of characters to the screen.

我们通过传递道具在示例的最后渲染FilteredList组件。 它根据side道具从道具中过滤角色列表。 然后将过滤后的列表作为道具传递给renderDisplayList,后者随后将字符列表渲染到屏幕上。

引入高阶组件 (Introducing higher-order components)

Let’s now talk about the nature of the higher-order function withFilterProps. In React’s vocabulary, such a function is called a higher-order component (HoC). Just as the higher-order function creates a new function, the HoC creates a new component.

现在让我们讨论withFilterProps的高阶函数的withFilterProps 。 在React的词汇表中,这种功能称为高阶组件(HoC)。 就像高阶函数创建新函数一样,HoC也创建新组件。

HoC is a function that accepts a component and returns a new component that renders the passed one. This new component is enhanced with an additional functionality.

HoC是一个接受 一个组件返回一个新的组件函数该组件 呈现传递的组件 此新组件通过附加功能得到增强。

const HoC = BaseComponent => EnhancedComponent

In our example, the withFilterProps HoC takes the renderDisplayList component and returns a new functional component that renders the renderDisplayList. The renderDisplayList component is enhanced with the filtering props logic.

在我们的示例中, withFilterProps HoC接受renderDisplayList组件,并返回一个呈现renderDisplayList的新功能组件。 renderDisplayList组件通过过滤道具逻辑得到增强。

Because we abstracted all the logic to the HoC, our base functional component only takes care of the UI rendering and is reusable again.

因为我们将所有逻辑抽象到了HoC,所以我们的基本功能组件仅负责UI渲染,并且可以再次使用。

The HoC is a special type of a function that wraps the presentational component and enhances it with an advanced functionality. Think of them as the wrappers for your functional components.

HoC是一种特殊类型的功能,它包装了演示组件并通过高级功能对其进行了增强。 将它们视为功能组件包装。

Thanks to the HoC pattern, you can enhance your simple functional components with whatever logic you want. This is the power of the HoC pattern. You can edit/update/transform props, maintain internal state, or affect the component rendering outside of your presentational component.

借助HoC模式,您可以使用所需的任何逻辑来增强简单的功能组件。 这就是HoC模式的力量。 您可以编辑/更新/变换道具,维持内部状态或影响外观组件之外的组件渲染。

Sticking to this pattern will enable you to use only functional components as your base components throughout your application and get rid of all the class components.

坚持这种模式将使您仅在整个应用程序中将功能组件用作基本组件,并摆脱所有类组件。

If we again consider the distinction between smart and presentational components, the base component will always be the presentational one (since it’s just a pure function). On the other hand, the HoC will take the role of a smart component since it deals only with the logic, which is then passed down to the presentational component. However, if you don’t need the class-specific behavior, you can also define HoC as a functional component (as you’ve just seen).

如果我们再次考虑智能组件和表示组件之间的区别,则基本组件将始终是表示组件(因为它只是一个纯函数)。 另一方面,由于HoC仅处理逻辑,然后将其传递给表示组件,因此HoC将充当智能组件的角色。 但是,如果您不需要特定于类的行为,则还可以将HoC定义为功能组件(如您所见)。

Since you made it this far, let’s slow down a little bit and talk about food :)

既然您已经做到了这一点,让我们放慢一点并谈论食物:)

肉饼或薄煎饼 (Meatloaf or Pancake)

At the beginning of this article, we saw this hard-to-reuse component that takes care of all the logic and presentation.

在本文的开头,我们看到了这个难以重用的组件,它负责所有逻辑和表示。

class FilteredList extends React.Component {   constructor(props) {      super(props)      this.state = { value: this.props.defaultState }   }   updateState(value) {      this.setState({ value })   }   render() {      const otherSide = this.state.value === 'dark' ? 'light' : 'dark'      const transformedProps = this.props.list.filter(char =&gt; char.side === this.state.value)      return (         <div>            <button onClick={() => this.updateState(otherSide)}>Switch</button>            {transformedProps.map(char =&gt;               <div key={char.name}>                  <div>Character: {char.name}</div>                  <div>Side: {char.side}</div>               </div>            )}         </div>      )   }}
ReactDOM.render (   <FilteredList defaultState='dark' list={starWarsChars} />,   document.getElementById('app'))

You can think of this component as meatloaf.

您可以将此组件视为肉饼

When preparing meatloaf, you take the meat, breadcrumbs, garlic, onion, and eggs, mix them together, put the raw meatloaf into the oven, and wait until it’s cooked. There’s no way that you can take the eggs or the onion from the meatloaf, since everything is irrevocably combined together.

准备肉饼时,您将肉,面包屑,大蒜,洋葱和鸡蛋混合在一起,将生的肉饼放入烤箱中,等待煮熟。 您无法从肉饼中取出鸡蛋或洋葱,因为所有内容都不可撤销地结合在一起。

This is the same as a component that is a mixture of logic and UI. You just can’t take something from it. You need to use it as is or not at all.

这与混合以下成分的成分相同 逻辑和UI。 您只是不能从中得到一些东西。 您需要按原样使用或根本不使用它。

Try to think of the presentational components as pancakes.

尝试将呈现组件视为薄煎饼

However, simple pancakes without any decoration are pretty boring, and no one eats them like this anyway. So you want to decorate them. You can pour maple syrup on them or put some berries or chocolate on top of them. So many possible decorating layers for you to use!

但是,没有任何装饰的简单煎饼很无聊,反正没人会这样吃它们。 因此,您要装饰它们。 您可以将枫糖浆倒在上面,或在上面放一些浆果或巧克力。 有这么多可能的装饰层供您使用!

In the React application, these decorating layers are represented by the HoCs. So, just as you decorate a pancake according to your taste, you also decorate the presentational component using HoC with the functionality you want. As a result, you can reuse a particular presentational component in different places of your application and decorate it with the HoC you want for a particular case.

在React应用程序中,这些装饰层由HoC表示。 因此,就像您根据自己的口味装饰煎饼一样,您还可以使用具有所需功能的HoC装饰演示组件。 因此, 您可以在应用程序的不同位置重用特定的呈现组件,并使用特定情况下所需的HoC对其进行修饰。

However, you can’t do that with the component that is responsible for all the logic and presentation, since everything is irrevocably combined together.

但是,您不能使用负责所有逻辑和表示的组件来执行此操作,因为所有内容都不可撤销地组合在一起。

I hope that this metaphor gave you a better understanding of the HoC pattern. If not, at least I made you hungry :).

我希望这个比喻能使您更好地理解HoC模式。 如果没有,至少我让你饿了。

使所有组件可重复使用 (Make all the components reusable again)

Now, that we know how to create a HoC, we’ll take a look at how to make it reusable.

现在,我们知道如何创建HoC,我们将研究如何使其可重用。

Making components reusable means to decouple them from the data. This means that they shouldn’t be dependent on a particular props structure. Sticking to reusable components helps you to avoid unnecessary duplication. You just pass a different set of props each time.

使组件可重用意味着将它们与数据分离 。 这意味着它们不应该依赖于特定的道具结构。 坚持使用可重复使用的组件可以帮助您避免不必要的重复。 每次您只需传递一组不同的道具。

By using the HoC pattern in the previous example, we moved all the logic to the HoC, and just let the base component render the UI. As a result, our presentational component became reusable since it just receives data as props and renders it to the screen.

通过使用上一个示例中的HoC模式,我们将所有逻辑都移到了HoC,只让基本组件呈现了UI。 结果,我们的演示组件变得可重用,因为它只是接收数据作为道具并将其呈现到屏幕上。

But it would be pretty difficult to reuse our HoC as well since it’s too specific.

但是,由于它太具体了,因此很难重用我们的HoC。

const withFilterProps = BaseComponent =&gt; ({ list, side }) => {   const transformedProps = list.filter(char => char.side === side)   return <BaseComponent list={transformedProps} />}

It can be applied only in the cases where the list and side props are present. You don’t want this kind of specificity in your application since you want reusable HoCs that can be used in various scenarios.

它只能在存在listside道具的情况下应用。 您不希望在应用程序中具有这种特殊性,因为您想要可在各种情况下使用的可重用HoC。

Let’s make the HoC reusable.

让我们使HoC可重用。

const withTransformProps = transformFunc =&gt; {   const ConfiguredComponent = BaseComponent => {      return baseProps => {         const transformedProps = transformFunc(baseProps)         return <BaseComponent {...transformedProps} />      }   }   return ConfiguredComponent}
const renderDisplayList = ({ list }) =>   <div>      {list.map(char =&gt;         <div key={char.name}>            <div>Character: {char.name}</div>            <div>Side: {char.side}</div>         </div>      )}   </div>
const FilteredList = withTransformProps(   ({ list, side }) =&gt; ({      list: list.filter(FilteredListchar =>         char.side === side)   }))(renderDisplayList)
ReactDOM.render (   <FilteredList      side='dark'      list={starWarsChars}   />,   document.getElementById('app'))

This code still does the same thing as the previous HoC example. We filter the props using the HoC component and then pass them to the base component. However, the old name would be misleading, since the HoC is no longer limited only to the filtering logic, so we renamed it withTransformProps.

这段代码仍然与之前的HoC示例相同。 我们使用HoC组件过滤道具,然后将其传递给基本组件。 但是,旧名称会引起误解,因为HoC不再仅局限于过滤逻辑,因此我们使用withTransformProps将其重命名。

We also no longer care about the props structure. We are newly passing a transformFunc as a configuration function to the withTransformProps. This function is responsible for the props transformation.

我们也不再关心道具的结构。 我们新近将一个transformFunc作为配置函数传递给withTransformProps 。 此功能负责道具的转换。

Let’s take a look at the FilteredList enhanced component. It gets created when we pass the configuration function (responsible for the props transformation) to the withTransformProps. We get back a specialized HoC with the transformation function stored inside the closure. We store it as the ConfiguredComponent. It expects the BaseComponent to be passed. When the renderDisplayList is passed to it, we get back a functional component that is waiting for the props to be passed. We store this enhanced component as the FilteredList.

让我们看一下FilteredList增强组件。 当我们将配置函数(负责props转换)传递给withTransformProps时,将创建它。 我们返回一个特殊的HoC,其转换函数存储在闭包内部。 我们将其存储为ConfiguredComponent 。 它期望通过BaseComponent 。 当将renderDisplayList传递给它时,我们得到一个正在等待道具传递的功能组件。 我们将此增强组件存储为FilteredList

The props get passed when we render the FilteredList component. Then, the transforming function we passed earlier takes the props and filters the characters according the side. The returned value is then passed as the props to the renderDisplayList base component which renders filtered Start Wars characters to the screen.

当我们渲染FilteredList组件时,道具会通过。 然后,我们前面传递的转换函数将道具并根据侧面过滤字符。 然后将返回的值作为道具传递给renderDisplayList基本组件,该组件将过滤后的Start Wars字符渲染到屏幕上。

However, our HoC syntax is pretty verbose. We don’t need to store the specialized HoC as the ConfiguredComponent inside a variable.

但是,我们的HoC语法非常冗长。 我们不需要将专门的HoC作为ConfiguredComponent存储在变量内。

const withTransformProps = mapperFunc =>   BaseComponent => baseProps => {      const transformedProps = mapperFunc(baseProps)      return <BaseComponent {...transformedProps} />   }

This solution is much cleaner.

此解决方案更加清洁。

The idea behind this approach is to have a reusable HoC that can be configured for any scenario in which we want to do something with the props before they get passed to the base component. That’s a powerful abstraction, isn’t it?

这种方法背后的想法是拥有一个可重用的HoC,该HoC可以针对在我们想要将道具传递给基本组件之前要对其进行处理的任何场景进行配置 。 那是一个强大的抽象,不是吗?

In our example, we passed a custom filtering function that could be different for every use case. And if we later decide that we want to change some of the HoC’s behavior, we just need to change it in a single reusable component and not in many different places of our application.

在我们的示例中,我们传递了一个自定义过滤功能,该功能对于每个用例可能都不同。 而且,如果以后我们决定要更改HoC的某些行为,则只需在单个可重用组件中更改它,而无需在应用程序的许多不同位置进行更改。

const HoC = config => BaseComponent => EnhancedComponent

The HoC and the base component are both reusable and independent of each other. The HoC doesn’t know where its data goes and the presentational component has no idea where its data is coming from.

HoC和基本组件都是可重用的,并且彼此独立 。 HoC不知道其数据来自何处,而表示组件也不知道其数据来自何处。

Writing reusable HoCs and presentational components will help you to avoid unnecessary repetition and force you to write simpler components. As a result, you’ll be writing cleaner, maintainable, and readable code.

编写可重用的HoC和表示性组件将帮助您避免不必要的重复,并迫使您编写更简单的组件。 结果,您将编写更干净,可维护且易读的代码。

Congratulations! By now you should be able to write reusable higher-order components yourself.

恭喜你! 现在,您应该可以自己编写可重用的高阶组件了。

In the following sections, you’ll learn the difference between class HoC and the functional one. We’ll also spend a good amount of time understanding how the composition of several higher-order components works. All of this will allow us to enhance our base components with even more behavior that can be easily reused throughout our application.

在以下各节中,您将学习HoC类和功能类之间的区别。 我们还将花费大量时间来理解几个高阶组件的组成如何工作。 所有这些将使我们能够通过更多行为来增强我们的基本组件,这些行为可以在整个应用程序中轻松重用。

功能或基于类的HoC? (Functional or class-based HoCs?)

Let’s talk a little bit about the difference between functional HoCs and class-based ones. When is it more convenient to stick to the former and when should you go for the latter?

让我们谈谈功能HoC与基于类的HoC之间的区别。 什么时候坚持前者更方便,什么时候该选择后者?

Since we want to follow the principles of FP, we should be using functional components as much as possible. We’re already doing this with presentational components as we’ve seen above. And we should do this with HoCs as well.

因为我们要遵循FP的原理,所以我们应该尽可能地使用功能组件 。 如上所述,我们已经在使用演示组件进行了此操作。 我们也应该通过HoC来做到这一点。

功能中心 (Functional HoC)

A functional HoC just wraps the base component, injects it with new props along with the original ones, and returns a new component. It doesn’t change the original component by modifying its prototype as the classes do. We saw such an HoC above. Here’s a quick reminder:

功能齐全的HoC只需包装基本组件,将其与原始道具一起注入新道具,然后返回一个新组件。 它不会像类一样通过修改其原型来更改原始组件。 我们在上面看到了这样的HoC。 快速提醒:

const withTransformProps = mapperFunc =>   BaseComponent => baseProps => {      const transformedProps = mapperFunc(baseProps)      return <BaseComponent {...transformedProps} />   }

This HoC doesn’t have any side effects. It doesn’t mutate anything. It’s a pure function.

此HoC没有任何副作用。 它不会改变任何东西。 这是一个纯函数。

When creating an HoC, we should define it as a functional component if possible.

创建HoC时,应尽可能将其定义为功能组件。

基于类的HoC (Class-based HoCs)

However, sooner or later, you’ll need to access the internal state or lifecycle methods in your component. You can’t achieve this without classes since this behavior is inherited from the React.Component, which can’t be accessed within the functional component. So, let’s define a class-based HoC.

但是,迟早需要访问组件中的内部状态或生命周期方法。 没有类,您将无法实现,因为此行为是从React.Component继承的,而React.Component不能在功能组件中访问。 因此,让我们定义一个基于类的HoC。

const withSimpleState = defaultState =&gt; BaseComponent => {   return class WithSimpleState extends React.Component {      constructor(props) {         super(props)         this.state = { value: defaultState }         this.updateState = this.updateState.bind(this)      }      updateState(value) {         this.setState({ value })      }      render() {         return (            <BaseComponent               {...this.props}               stateValue={this.state.value}               stateHandler={this.updateState}            />         )      }   }}
const renderDisplayList = ({ list, stateValue, stateHandler })=&gt; {   const filteredList = list.filter(char => char.side === stateValue)   const otherSide = stateValue === 'dark' ? 'light' : 'dark'   return (      <div>         <;button onClick={() => stateHandler(otherSide)}>Switch</button>         {filteredList.map(char =>            <div key={char.name}>               <div>Character: {char.name}</div>               <div>Side: {char.side}</div>            </div>         )}      </div>   )}
const FilteredList = withSimpleState('dark')(renderDisplayList)
ReactDOM.render (   <FilteredList list={starWarsChars} />,   document.getElementById('app'))

Our new class-based HoC withSimpleState expects a configuration parameter defaultState which is pretty self-explanatory. It also maintains a state named value and defines an event handler updateState that can set the value of the state. Finally, it passes the state utilities along with the original props to the base component.

我们新的基于类的HoC withSimpleState期望一个配置参数defaultState ,这很withSimpleState解释。 它还维护一个名为value的状态,并定义一个可以设置状态value的事件处理程序updateState 。 最后,它将状态实用程序以及原始道具传递给基本组件。

renderDisplayList now contains filtering logic that was previously stored inside the withTransformProps HoC, so it’s not reusable anymore.

renderDisplayList现在包含以前存储在withTransformProps HoC中的过滤逻辑,因此它不再可重用。

Let’s take a look at the FilteredList component. First, we pass the configuration string dark to the withSimpleState and get back a specialized HoC waiting for the base component. So, we pass it the renderDisplayList component and get back a class component waiting for the props to be passed. We store this component as the FilteredList.

让我们看一下FilteredList组件。 首先,我们将dark的配置字符串传递给withSimpleState ,然后返回专门的HoC,等待基本组件。 因此,我们将其传递给renderDisplayList组件,并返回一个类组件,以等待传递道具。 我们将此组件存储为FilteredList

At the end of the example, we render the component by passing the props to it. When this happens, the class component sets the state value to dark and passes the state and its handler to the renderDisplayList component along with the list prop.

在示例的最后,我们通过将道具传递给组件来渲染组件。 发生这种情况时,类组件将状态valuedark ,并将状态及其处理程序与list属性一起传递给renderDisplayList组件。

renderDisplayList then filters the list prop according to the passed state value and sets the otherSide variable. Finally, it renders the filtered list to the screen along with the button with the attached state handler. When the button is clicked, the state is set to the otherSide variable.

然后, renderDisplayList根据传递的状态值过滤list otherSide并设置otherSide变量。 最后,它将筛选的列表与带有附加状态处理程序的按钮一起呈现到屏幕上。 单击按钮后,状态将设置为otherSide变量。

有关系吗? (Does it matter?)

As you’ve just seen, our new HoC withSimpleState returns a class, instead of a functional component. You might say it doesn’t look like a pure function since it contains impure class-specific behavior (state). However, let’s take a closer look.

如您所见,我们的新HoC withSimpleState返回一个类,而不是一个功能组件。 您可能会说它看起来不像是纯函数,因为它包含不纯的类特定行为(状态)。 但是,让我们仔细看看。

withSimpleState doesn’t have any side effects. It doesn’t mutate anything. It just takes the base component and returns a new one. Although it contains the impure class-related code, the HoC itself is still a pure function since “the purity of a function is judged from the outside, regardless of what goes on inside.” We are basically hiding the class-specific impure code inside the HoC pure function.

withSimpleState没有任何副作用。 它不会改变任何东西。 它只需要基本组件并返回一个新组件。 尽管HoC包含与类无关的代码,但是HoC本身仍然是纯函数,因为“函数的纯度是从外部判断的, 而不管内部发生了什么 。” 我们基本上是将特定于类的不纯代码隐藏在HoC纯函数中。

The HoC (pure function) enables us to encapsulate the impure class-related code inside of it.

HoC(纯函数)使我们能够在其中封装不纯的类相关代码。

If you find yourself in a situation where you simply can’t write a functional component because you need a class-related behavior, wrap the impure code inside the HoC, which is the pure function instead, just as we did in the example.

如果您由于需要类相关的行为而陷入无法编写功能组件的境地,请将不纯的代码包装在HoC中,这是纯函数,就像在示例中所做的那样。

下一步是什么? (What’s next?)

If you check our example again, you’ll see that we have a new problem. The renderDisplayList component is no longer reusable since we moved the filtering logic inside it.

如果再次检查我们的示例,您将看到我们有一个新问题。 renderDisplayList组件不再可重用,因为我们在其中移动了过滤逻辑。

To make it reusable again, we need to move the logic back to the withTransformProps HoC. To achieve this, we need to figure out how to use the withTransformProps and withSimpleState HoCs with the base component at the same time and allow the renderDisplayList to only be responsible for the presentation again. We can achieve this behavior using composition.

为了使其可重复使用,我们需要将逻辑移回到withTransformProps HoC。 为此,我们需要弄清楚如何同时将withTransformPropswithSimpleState HoC与基本组件一起使用,并允许renderDisplayList仅再次负责呈现。 我们可以使用合成来实现这种行为。

组成 (Composition)

We’ve already talked about the composition principle at the beginning. It enables us to combine several functions into a new compound function. Here’s a quick reminder:

我们一开始就已经讨论了合成原理。 它使我们能够将几个功能组合为新的复合功能。 快速提醒:

const number = 15const increment = num => num + 5const decrement = num => num - 3const multiply = num => num * 2
const operation = increment(decrement(multiply(number)))console.log(operation)  //32

We have a number and three functions. We wrap them all inside each other, and we get a compound function to which we pass the number.

我们有一个数字和三个功能。 我们将它们全部包装在一起,然后得到一个传递数字的复合函数。

This works fine. However, the readability might get worse, if we wanted to compose even more functions. Fortunately, we can define a functional programming compose function to help us out. Keep in mind that it composes functions from right to left.

这很好。 但是,如果我们想编写更多函数,则可读性可能会变差。 幸运的是,我们可以定义函数式编程compose函数来帮助我们。 请记住,它是从右到左组成的功能。

const compose = (...funcs) =&gt; value =&gt;   funcs.reduceRight((acc, func) => func(acc)      , value)
const number = 15const increment = num =&gt; num + 5const decrement = num =>; num - 3const multiply = num =&gt; num * 2
const funcComposition = compose(   increment,   decrement,   multiply)
const result = funcComposition(number)console.log(result)  //32

We no longer need to explicitly wrap the functions inside each other. Instead, we pass them all as the arguments to the compose function. When we do that, we get back a new compound function waiting for the value argument to be passed. We store it as a funcComposition.

我们不再需要显式地将函数彼此包装在一起。 相反,我们将它们全部作为参数传递给compose函数。 当我们这样做时,我们将返回一个新的复合函数,等待传递value参数。 我们将其存储为funcComposition

Finally, we pass the number as the value to the funcComposition function. When this happens, the compose passes the value to the multiply (rightmost) function. The returned value is then passed as an input to the decrement function and so on until all the functions in the composition have been called. We store the final value as a result.

最后,我们将number作为value传递给funcComposition函数。 发生这种情况时, composevalue传递给multiply (最右边)函数。 然后,将返回值作为输入传递给decrement函数,依此类推,直到调用了组合中的所有函数。 我们将最终值存储为result

特委会的组成 (Composition of HoCs)

Let’s take a look at how we could compose several HoCs. We’ve already learned that our reusable HoCs should only be responsible for a single task. However, what if we needed to implement complex logic that can’t be stored in a single HoC? To achieve this, we want to be able to combine several HoCs together and wrap them around the base component.

让我们看一下如何compose几个HoC。 我们已经了解到,可重复使用的HoC应该仅负责一项任务。 但是,如果我们需要实现无法存储在单个HoC中的复杂逻辑怎么办? 为了实现这一点,我们希望能够将多个HoC组合在一起并将它们包装在基本组件周围。

First, let’s take a look at the HoC composition without a compose helper since it’s easier to understand what’s going on.

首先,让我们看看没有compose帮助器的HoC组合,因为它更容易理解正在发生的事情。

const withTransformProps = mapperFunc =>   BaseComponent => baseProps => {      const transformedProps = mapperFunc(baseProps)      return <BaseComponent {...transformedProps} />   }
const withSimpleState = defaultState =&gt; BaseComponent => {   return class WithSimpleState extends React.Component {      constructor(props) {         super(props)         this.state = { value: defaultState }         this.updateState = this.updateState.bind(this)      }      updateState(value) {         this.setState({ value })      }      render() {         return (            <BaseComponent               {...this.props}               stateValue={this.state.value}               stateHandler={this.updateState}            />         )      }   }}
const renderDisplayList = ({ list, stateHandler, otherSide }) =&gt; (   <div>      <button onClick={() => stateHandler(otherSide)}>Switch</button&gt;      {list.map(char =>         <div key={char.name}>            <div>Character: {char.name}</div>            <div>Side: {char.side}</div>         </div>      )}   </div>)
const FilteredList = withTransformProps(({ list, stateValue, stateHandler }) =&gt; {   const otherSide = stateValue === 'dark' ? 'light' : 'dark'   return {      stateHandler,      otherSide,      list: list.filter(char => char.side === stateValue),   }})(renderDisplayList)
const ToggleableFilteredList = withSimpleState('dark')(FilteredList)
ReactDOM.render (   <ToggleableFilteredList list={starWarsChars} />,   document.getElementById('app'))

Nothing new here. We’ve seen all this code before. The new thing is that we are composing two HoCs — withSimpleState which provides us with the state utilities and withTransformProps which gives us the props transformation functionality.

这里没有新内容。 我们之前已经看过所有这些代码。 新的事情是,我们正在组成两个HoC- withSimpleState为我们提供状态实用程序,以及withTransformProps为我们提供道具转换功能。

We have two enhanced components here: FilteredList and ToggleableFilteredList.

这里有两个增强的组件: FilteredListToggleableFilteredList

First, we enhance the renderDisplayList component with the withTransformProps HoC and store it as the FilteredList. Secondly, we enhance the new FilteredList component using the withSimpleState HoC and store it as the ToggleableFilteredList.

首先,我们使用withTransformProps HoC增强renderDisplayList组件,并将其存储为FilteredList 。 其次,我们使用withSimpleState HoC增强了新的FilteredList组件,并将其存储为ToggleableFilteredList

ToggleableFilteredList is a component enhanced by two HoCs that have been composed together.

ToggleableFilteredList是由两个组成在一起的HoC增强的组件。

Here’s a detailed description of the HoC composition:

这是HoC组成的详细说明:

  1. We pass a props transformation function to the withTransformProps HoC and get back a specialized HoC waiting for the base component to be passed.

    我们将props转换函数传递给withTransformProps HoC,然后返回专门的HoC,等待基础组件通过。

  2. We pass it the renderDisplayList presentational component and get back a new functional component expecting the props argument.

    我们将其传递给renderDisplayList表示组件,并获得一个期待props参数的新功能组件。

  3. We store this enhanced component as the FilteredList.

    我们将此增强组件存储为FilteredList

  4. We pass the dark string to the withSimpleState HoC and get back a specialized HoC waiting for the base component to be passed.

    我们将dark字符串传递给withSimpleState HoC,然后返回专门的HoC,等待传递基本组件。

  5. We pass it our enhanced FilteredList component as the base component and we get back a class component waiting for the props.

    我们将增强的FilteredList组件作为基本组件传递给它,然后返回一个等待道具的类组件。

  6. We store this higher-order component composition as the ToggleableFilteredList.

    我们将此高阶组件组成存储为ToggleableFilteredList

  7. We render the ToggleableFilteredList component by passing the list props to it.

    我们通过将list道具传递给它来渲染ToggleableFilteredList组件。

  8. ToggleableFilteredList is the FilteredList component enhanced by the withSimpleState HoC. So, the props are first passed to the class component that was returned by this HoC. Inside it, the props get enhanced with a state and its handler. These props along with the original ones are then passed to the FilteredList as the base component.

    ToggleableFilteredListwithSimpleState HoC增强的FilteredList组件。 因此,道具首先传递到此HoC返回的类组件。 在道具内部,道具通过状态及其处理程序得到增强。 然后将这些道具与原始道具一起作为基本组件传递到FilteredList

  9. FilteredList is a renderDisplayList component enhanced by the withTransformProps HoC. So, the props are first passed to the functional component that was returned by this HoC. Inside it, the passed list prop is filtered using the transformation function. These props along with the other props are then passed to the base component renderDisplayList.

    FilteredList是一个由withTransformProps HoC增强的renderDisplayList组件。 因此,道具首先传递到此HoC返回的功能组件。 在其中,使用转换函数对传递的list属性进行过滤。 然后,这些道具与其他道具一起传递给基本组件renderDisplayList

  10. Finally, the renderDisplayList component renders the list of the characters with the switch button to the screen.

    最后, renderDisplayList组件使用切换按钮将字符列表呈现到屏幕。

The composition lets us enhance our base component with the functionality aggregated from several HoCs.

通过这种组合,我们可以利用几个HoC聚合的功能来增强基本组件。

In our example, we passed the new behavior from the withSimpleState and withTransformProps HoCs to the renderDisplayList base component.

在我们的示例中,我们将新行为从withSimpleStatewithTransformProps HoC传递给renderDisplayList基本组件。

As you’ve just seen, the props are the only language that HoCs use to talk to each other inside a composition. Each HoC performs a specific action which results in an enhancement or a modification of the props object.

如您所见, 道具是HoC在构图中用于彼此交谈的唯一语言 。 每个HoC都会执行特定操作,从而导致对props对象的增强或修改。

重构 (Refactor)

Although our HoC composition works, the syntax itself is pretty verbose. We can make it simpler by getting rid of the ToggleableFilteredList variable and just wrap the HoCs inside each other.

尽管我们的HoC组合有效,但语法本身非常冗长。 通过摆脱ToggleableFilteredList变量,使HoC相互包裹,可以使其更简单。

const FilteredList = withSimpleState('dark')(   withTransformProps(({ list, stateValue, stateHandler }) =&gt; {      const otherSide = stateValue === 'dark' ? 'light' : 'dark'      return {         stateHandler,         otherSide,         list: list.filter(char => char.side === stateValue),      }   })(renderDisplayList))

This code is a little bit better. However, we are still manually wrapping all the components. Imagine that you wanted to add even more HoCs to this composition. In such a case, our composition will become difficult to read and understand. Just imagine all those parentheses!

这段代码要好一些。 但是,我们仍在手动包装所有组件。 想象一下,您想为此组合添加更多的HoC。 在这种情况下,我们的作品将变得难以阅读和理解。 试想一下所有这些括号!

使用撰写 (Using compose)

Since this talk is about FP principles, let’s use the compose helper.

由于本演讲是关于FP原理的,因此让我们使用compose助手。

const compose = (...hocs) =&gt; BaseComponent =&gt;   hocs.reduceRight((acc, hoc) => hoc(acc)      , BaseComponent)
const enhance = compose(   withSimpleState('dark'),   withTransformProps(({ list, stateValue, stateHandler }) =&gt; {      const otherSide = stateValue === 'dark' ? 'light' : 'dark'      return {         stateHandler,         otherSide,         list: list.filter(char => char.side === stateValue),      }   }))
const FilteredList = enhance(renderDisplayList)

We no longer need to explicitly wrap the HoCs inside each other. Instead, we pass them all as the arguments to the compose function. When we do that, we get back a new compound function waiting for the BaseComponent argument to be passed. We store this function as enhance. Then, we just pass the renderDisplayList as the base component to it, and compose will do all the component wrapping for us.

我们不再需要显式地将HoC彼此包装在一起。 相反,我们将它们全部作为参数传递给compose函数。 当我们这样做时,我们将返回一个新的复合函数,等待传递BaseComponent参数。 我们将此功能存储为enhance 。 然后,我们只将renderDisplayList作为基本组件传递给它,然后compose将为我们完成所有组件包装。

再次煎饼 (Pancakes again)

I’d like to come back to our pancake analogy. Before, we were decorating our pancakes with only a single flavorful layer. But as we all know, pancakes taste much better when you combine more flavors together. How about a pancake with melted chocolate and banana or with cream and caramel? You know what I’m talking about…

我想回到煎饼的比喻。 以前,我们只用一层香精装饰煎饼。 但是众所周知,当您将更多口味结合在一起时,煎饼的味道要好得多。 一块融化的巧克力和香蕉或奶油和焦糖的煎饼怎么样? 你懂我在说什么…

Just as you can decorate your pancake using one or several decorating layers depending on your tastes, you can decorate your presentational component with one or several HoCs to get the combination of logic you want for your particular use case.

正如您可以根据自己的口味使用一层或多层装饰层来装饰薄煎饼一样,您可以使用一个或多个HoC来装饰演示组件,以获得特定用例所需的逻辑组合。

If you need a complex logic for your presentational component, you don’t need to store it all inside a single component or in a single HoC. Instead, you just compose several simple HoCs together and enhance your presentational component with them.

如果您的演示组件需要复杂的逻辑,则无需将其全部存储在单个组件或单个HoC中。 相反,您只需将几个简单的HoC组合在一起,并使用它们增强您的演示组件。

重新组合 (Recompose)

So far, you’ve seen some simple HoCs. However, this pattern is so powerful that it has been used in many React-based libraries (such as React-Redux, React router, Recompose).

到目前为止,您已经看到了一些简单的HoC。 但是,这种模式非常强大,以至于已在许多基于React的库中使用(例如React-Redux,React router,Recompose)。

I’d like to talk more about the Recompose library, which provides us with dozens of HoCs. It uses HoCs for everything from state and lifecycle to conditional rendering and props manipulation.

我想进一步谈谈Recompose ,它为我们提供了数十种HoC。 它使用HoC进行从状态和生命周期到条件渲染和道具操纵的所有操作。

Let’s rewrite our HoC composition example using the predefined HoCs from Recompose.

让我们使用Recompose中预定义的HoC重写我们的HoC组成示例。

import { withState, mapProps, compose } from 'recompose';
const enhance = compose(   withState('stateValue', 'stateHandler', 'dark'),   mapProps(({ list, stateValue, stateHandler }) =&gt; {      const otherSide = stateValue === 'dark' ? 'light' : 'dark'      return {         stateHandler,         otherSide,         list: list.filter(char => char.side === stateValue),      }   }),)
const FilteredList = enhance(renderDisplayList)
ReactDOM.render (   <FilteredList list={starWarsChars} />,   document.getElementById('app'))

Our two custom HoCs withSimpleState and withTransformProps are already predefined in Recompose as withState and mapProps. Moreover, the library also provides us with a predefined compose function. So, it’s really easy just to use these existing implementations, rather than defining our own.

我们的两个自定义HoC withSimpleStatewithTransformProps已在withTransformProps中预先定义为withStatemapProps 。 此外,该库还为我们提供了预定义的compose功能。 因此,仅使用这些现有的实现而不是定义我们自己的实现就非常容易。

The Recompose version of the HoC composition isn’t that different from ours. Just the withState HoC is now more reusable since it takes three arguments, where you can set the default value of the state, the state name, and the name of its handler as well. mapProps works the same way as our implementation. We only need to pass the configuration function.

HoC合成的Recompose版本与我们的版本没有什么不同。 现在,只有withState HoC可以重用,因为它带有三个参数,您可以在其中设置状态的默认值,状态名称以及其处理程序的名称。 mapProps工作方式与我们的实现相同。 我们只需要传递配置功能。

As a result, we don’t need to define HoCs, which provide us with a general behavior.

结果,我们不需要定义HoC,它可以为我们提供一般的行为。

更多改进 (More improvements)

We can improve our composition using Recompose even more since there’s still one issue we haven’t addressed yet.

由于还有一个尚未解决的问题,我们甚至可以使用Recompose来改善构图。

const renderDisplayList = ({ list, stateHandler, otherSide }) =&gt; (   <div>      <button onClick={() => stateHandler(otherSide)}>Switch</button&gt;      {list.map(char =>         <div key={char.name}>            <div>Character: {char.name}</div>            <div>Side: {char.side}</div>         </div>      )}   </div>)

If we check the renderDisplayList component again, we can see that it’s click handler function gets recreated each time the component re-renders. And we want to prevent any unnecessary recreation since it might hinder the performance of our application. Fortunately, we can add the withHandlers HoC to our composition to address this issue.

如果再次检查renderDisplayList组件,则可以看到每次重新渲染该组件时,都会重新创建其单击处理程序函数。 并且我们希望防止任何不必要的重新创建,因为这可能会阻碍我们的应用程序的性能。 幸运的是,我们可以将withHandlers HoC添加到我们的组合中以解决此问题。

import { withState, mapProps, withHandlers, compose } from 'recompose';
const renderDisplayList = ({ list, handleSetState }) =&gt; (   <div>      <button onClick={handleSetState}>Switch</button>      {list.map(char =>         <div key={char.name}>            <div>Character: {char.name}</div>            <div>Side: {char.side}</div>         </div>      )}   </div>)
const enhance = compose(   withState('stateValue', 'stateHandler', 'dark'),   mapProps(({ list, stateValue, stateHandler }) =&gt; {      const otherSide = stateValue === 'dark' ? 'light' : 'dark'      return {         stateHandler,         otherSide,         list: list.filter(char => char.side === stateValue),      }   }),   withHandlers({      handleSetState: ({ stateHandler, otherSide }) =&gt; () => stateHandler(otherSide)   }))
const FilteredList = enhance(renderDisplayList)
ReactDOM.render (   <FilteredList list={starWarsChars} />,   document.getElementById('app'))

withHandlers HoC takes an object of functions as a configuration argument. In our example, we pass an object with a single function handleSetState. When this happens, we get back an HoC expecting the base component and the props to be passed. When we pass them, the outer function in every key of the passed object receives the props object as an argument.

withHandlers HoC将函数对象作为配置参数。 在我们的示例中,我们传递了一个具有单个功能handleSetState的对象。 发生这种情况时,我们会返回一个HoC,期望基本组件和道具能够通过。 当我们传递它们时,传递的对象的每个键中的外部函数都将props对象作为参数接收。

In our case handleSetState function receives stateHandler and otherSide props. We get back a new function that is then injected to the props and is passed down to the renderDisplayList component.

在我们的例子中, handleSetState函数接收stateHandlerotherSide道具。 我们取回一个新函数,然后将其注入到props中,并向下传递给renderDisplayList组件。

The handleSetState then gets attached to the button in a way that doesn’t require its recreation during every component's re-render since the withHandlers makes sure that the identity of its handlers are preserved across renders. As a result, the handlers get recreated only when the props passed to the withHandlers change.

The handleSetState then gets attached to the button in a way that doesn't require its recreation during every component's re-render since the withHandlers makes sure that the identity of its handlers are preserved across renders. As a result, the handlers get recreated only when the props passed to the withHandlers change.

Of course, the possible recreation of our simple click handler function doesn’t hinder the performance much. withHandlers is much more useful when you need to optimize a higher number of complex handlers.

Of course, the possible recreation of our simple click handler function doesn't hinder the performance much. withHandlers is much more useful when you need to optimize a higher number of complex handlers.

This also means that it’s a good place for storing all the handlers used inside your presentational component. This way, it’s immediately obvious for anyone who looks at your component, which handlers are being used inside it. As a result, it’s pretty simple for a developer to add or remove a particular handler. This is much better than searching for all the handlers inside a component manually.

This also means that it's a good place for storing all the handlers used inside your presentational component. This way, it's immediately obvious for anyone who looks at your component, which handlers are being used inside it. As a result, it's pretty simple for a developer to add or remove a particular handler. This is much better than searching for all the handlers inside a component manually.

By providing us with many reusable HoCs, Recompose makes HoC composition and the usage of HoCs in general much easier, since we don’t need to write all the HoCs ourselves.

By providing us with many reusable HoCs, Recompose makes HoC composition and the usage of HoCs in general much easier, since we don't need to write all the HoCs ourselves.

In real-world applications, you’ll be using these predefined HoCs quite often since they cover most typical use cases. And in the case you need a specific logic that needs to be shared across several components, you’ll define an HoC yourself.

In real-world applications, you'll be using these predefined HoCs quite often since they cover most typical use cases. And in the case you need a specific logic that needs to be shared across several components, you'll define an HoC yourself.

结论 (Conclusion)

Thanks to the principles of functional programming we were able to transform this not reusable huge component from the beginning…

Thanks to the principles of functional programming we were able to transform this not reusable huge component from the beginning…

class FilteredList extends React.Component {   constructor(props) {      super(props)      this.state = { value: this.props.defaultState }   }   updateState(value) {      this.setState({ value })   }   render() {      const otherSide = this.state.value === 'dark' ? 'light' : 'dark'      const transformedProps = this.props.list.filter(char =&gt; char.side === this.state.value)      return (         <div>            <button onClick={() => this.updateState(otherSide)}>Switch</button>            {transformedProps.map(char =&gt;               <div key={char.name}>                  <div>Character: {char.name}</div>                  <div>Side: {char.side}</div>               </div>            )}         </div>      )   }}
ReactDOM.render (   <FilteredList defaultState='dark' list={starWarsChars} />,   document.getElementById('app'))

…into this reusable, readable, and maintainable component composition.

…into this reusable, readable, and maintainable component composition.

import { withState, mapProps, withHandlers, compose } from 'recompose';
const renderDisplayList = ({ list, handleSetState }) =&gt; (   <div>      <button onClick={handleSetState}>Switch</button>      {list.map(char =>         <div key={char.name}>            <div>Character: {char.name}</div>            <div>Side: {char.side}</div>         </div>      )}   </div>)
const enhance = compose(   withState('stateValue', 'stateHandler', 'dark'),   mapProps(({ list, stateValue, stateHandler }) =&gt; {      const otherSide = stateValue === 'dark' ? 'light' : 'dark'      return {         stateHandler,         otherSide,         list: list.filter(char => char.side === stateValue),      }   }),   withHandlers({      handleSetState: ({ stateHandler, otherSide }) =&gt; () => stateHandler(otherSide)   }))
const FilteredList = enhance(renderDisplayList)
ReactDOM.render (   <FilteredList list={starWarsChars} />,   document.getElementById('app'))

We use these principles during application development quite often. Our aim is to use simple reusable components as much as possible. The HoC pattern helps us to achieve this since its idea is to move the logic to the HoC and let the presentational functional component take care of the UI rendering. As a result, we don’t need to use classes for our presentational components anymore, only for the HoCs if we need a class-specific behavior.

We use these principles during application development quite often. Our aim is to use simple reusable components as much as possible. The HoC pattern helps us to achieve this since its idea is to move the logic to the HoC and let the presentational functional component take care of the UI rendering. As a result, we don't need to use classes for our presentational components anymore, only for the HoCs if we need a class-specific behavior.

As a result, our application is composed of a bunch of presentational components that we can reuse throughout our application, and we can enhance them using one or several reusable HoCs to get a logic we need for a particular scenario (such as a dedicated HoC for data fetching).

As a result, our application is composed of a bunch of presentational components that we can reuse throughout our application, and we can enhance them using one or several reusable HoCs to get a logic we need for a particular scenario (such as a dedicated HoC for data fetching).

A cool feature about our approach is that, if you take a look at a particular HoC composition, you immediately know what kind of logic it uses. You just need to check the compose function where you can see all the logic contained in the HoCs. If you decide to add more logic, you just insert a new HoC into the compose function. Furthermore, if you wanted to see what handlers the component uses, you just need to check the withHandlers HoC.

A cool feature about our approach is that, if you take a look at a particular HoC composition, you immediately know what kind of logic it uses. You just need to check the compose function where you can see all the logic contained in the HoCs. If you decide to add more logic, you just insert a new HoC into the compose function. Furthermore, if you wanted to see what handlers the component uses, you just need to check the withHandlers HoC.

Another cool thing about HoCs is that they’re not tied to React. This means you can use them in your other applications that haven’t been written in React.

Another cool thing about HoCs is that they're not tied to React. This means you can use them in your other applications that haven't been written in React.

Congratulations! You made it.

恭喜你! 你做到了。

If you liked this article, give it a few claps. I would greatly appreciate it and more people will be able to see this post as well.

如果您喜欢这篇文章,请给她一些鼓掌 我将不胜感激,更多的人也将能够看到这篇文章。

This post was originally published on my blog.

This post was originally published on my blog .

If you have any questions, criticism, observations, or tips for improvement, feel free to write a comment below or reach me via Twitter.

如果您有任何疑问,批评,意见或改进技巧,请随时在下面写评论或通过Twitter与我联系。

David Kopal (@coding_lawyer) | TwitterThe latest Tweets from David Kopal (@coding_lawyer). passionate programmer, speaker, former lawyer, love to learn new…twitter.com

David Kopal (@coding_lawyer) | Twitter The latest Tweets from David Kopal (@coding_lawyer). passionate programmer, speaker, former lawyer, love to learn new… twitter.com

翻译自: https://www.freecodecamp.org/news/higher-order-components-the-ultimate-guide-b453a68bb851/

组件和高阶组件区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值