Home  >  Article  >  Web Front-end  >  Explore best practices for introducing higher-order components in React

Explore best practices for introducing higher-order components in React

WBOY
WBOYOriginal
2023-08-31 23:49:021357browse

探索在 React 中引入高阶组件的最佳实践

This is the third part of the high-level components series. In this first tutorial, we start from scratch. We learned the basics of ES6 syntax, higher-order functions, and higher-order components.

Higher-order component patterns are useful for creating abstract components - you can use them to share data (state and behavior) with existing components. In the second part of this series, I demonstrate a practical example of code using this pattern. This includes protected routing, creating configurable generic containers, attaching loading indicators to components, and more.

In this tutorial, we'll look at some best practices and considerations you should consider when writing HOCs.

Introduction

React used to have something called Mixins, which worked well with the React.createClass method. Mixins allow developers to share code between components. However, they had some drawbacks and the idea was eventually abandoned. Mixins have not been upgraded to support ES6 classes, and Dan Abramov even wrote an in-depth article explaining why mixins are considered harmful.

Higher-order components emerged as a replacement for Mixins, and they support ES6 classes. Additionally, HOC doesn't require doing anything with the React API and is a general pattern that works well with React. However, HOCs also have drawbacks. Although the disadvantages of higher-order components may not be obvious in smaller projects, you can link multiple higher-order components to a single component, as shown below.

const SomeNewComponent = 
        withRouter(RequireAuth(LoaderDemo(GenericContainer(CustomForm(Form)))))

You shouldn't let the link get to the point where you're asking yourself the question: "Where do these props come from?" This tutorial addresses some common problems with the Higher-Order Component pattern and the solutions to solve them correctly.

HOC Question

Some common issues related to HOCs have less to do with the HOCs themselves and more to do with their implementation.

As you know, HOC is great for code abstraction and creating reusable code. However, when you have multiple HOCs stacked, it can be a pain to debug if something doesn't look right or certain props aren't showing up because React DevTools gives you very limited clues as to what might be going wrong.

Real World HOC Problems

To understand the disadvantages of HOCs, I created a sample demo nested with some of the HOCs we created in the previous tutorial. We have four higher-order functions wrapping a single ContactList component. If the code doesn't make sense or you didn't follow my previous tutorial, here's a short summary of how it works.

withRouter is a HOC and is part of the React-router package. It allows you to access the properties of the history object and then pass them as props.

withAuth Looks for the authentication property and renders WrappedComponent if authentication is true. If authentication is false, '/login' will be pushed to the history object.

withGenericContainer Accepts an object as input in addition to WrappedComponent. GenericContainer Makes an API call and stores the result in state, then sends the data as props to the wrapped component.

withLoader is a HOC with an additional loading indicator. The indicator rotates until the acquired data reaches the status.

BestPracticeDemo.jsx

class BestPracticesDemo extends Component {

    render() {

		return(
            <div className="contactApp">
    			<ExtendedContactList authenticated = {true} {...this.props} contacts ="this" />
    	    </div>
     	)
	}
}

const ContactList = ({contacts}) => {
	
	return(
		<div>
			<ul>
      {contacts.map(
        (contact) => <li key={contact.email}>
         
          <img src={contact.photo}    style="max-width:90%" height="100px"  alt="Explore best practices for introducing higher-order components in React" />
          <div className="contactData">
          <h4>{contact.name}</h4>
           <small>{contact.email}</small>  <br/><small> {contact.phone}</small>
          </div>
         
        </li>
      )}
    </ul>
		</div>
		)
}

const reqAPI = {reqUrl: 'https://demo1443058.mockable.io/users/', 
                reqMethod:'GET', resName:'contacts'}	

const ExtendedContactList = withRouter(
                                withAuth(
                                    withGenericContainer(reqAPI)(
                                        withLoader('contacts')
                                            (ContactList))));

export default BestPracticesDemo;

Now you can see for yourself some common pitfalls of higher-order components. Let’s discuss some of these in detail.

Basic Notes

Don’t forget to spread props in HOC

Suppose we have an authenticated = { this.state.authenticated } property at the top of the composition hierarchy. We know this is an important prop and should extend it all the way to the Explore best practices for introducing higher-order components in React component. However, imagine that an intermediate HOC, such as withGenericContainer, decides to ignore all of its props.

//render method of withGenericContainer
render() {
	return(
		<WrappedComponent />
    )
}

This is a very common mistake and should be avoided when writing higher-order components. People who are not familiar with HOCs may find it difficult to figure out why all the props are missing because it is difficult to isolate the problem. So, always remember to propagate props in your HOC.

//The right way

render() {
	return(
		<WrappedComponent {...this.props} {...this.state} />)
}

Do not pass non-existent props beyond the scope of the HOC

HOC may introduce new properties to WrappedComponent that may not be of any use. In this case, it's a good practice to pass props that are only relevant to the composed component.

Higher-order components can accept data in two ways: as parameters of functions or as props of components. For example, authenticated = { this.state.authenticated } is a prop example, while in withGenericContainer(reqAPI)(ContactList) we pass data as parameter.

因为 withGenericContainer 是一个函数,所以您可以根据需要传入任意数量的参数。在上面的示例中,配置对象用于指定组件的数据依赖性。然而,增强组件和包装组件之间的契约是严格通过 props 进行的。

因此,我建议通过函数参数填充静态时间数据依赖项,并将动态数据作为 props 传递。经过身份验证的道具是动态的,因为用户可以通过身份验证,也可以不通过身份验证,具体取决于他们是否登录,但我们可以确定 reqAPI 对象的内容不会动态更改。

不要在渲染方法中使用 HOC

这是一个您应该不惜一切代价避免的示例。

var OriginalComponent = () => <p>Hello world.</p>;

class App extends React.Component {
  render() {
    return React.createElement(enhanceComponent(OriginalComponent));
  }
};

除了性能问题之外,您还将在每次渲染时丢失 OriginalComponent 及其所有子组件的状态。要解决这个问题,请将 HOC 声明移到 render 方法之外,使其仅创建一次,以便渲染始终返回相同的EnhancedComponent。

var OriginalComponent = () => <p>Hello world.</p>;
var EnhancedComponent = enhanceComponent(OriginalComponent);

class App extends React.Component {
  render() {
    return React.createElement(EnhancedComponent);
  }
};

不要改变包装组件

改变 HOC 内的包装组件将导致无法在 HOC 外部使用包装组件。如果您的 HOC 返回 WrappedComponent,您几乎总是可以确定自己做错了。下面的例子演示了突变和组合之间的区别。

function logger(WrappedComponent) {
 WrappedComponent.prototype.componentWillReceiveProps = function(nextProps) {
    console.log('Current props: ', this.props);
    console.log('Next props: ', nextProps);
  };
  // We're returning the WrappedComponent rather than composing
  //it
  return WrappedComponent;
}

组合是 React 的基本特征之一。您可以在其渲染函数中将一个组件包装在另一个组件内,这就是所谓的组合。

function logger(WrappedComponent) {
  return class extends Component {
    componentWillReceiveProps(nextProps) {
      console.log('Current props: ', this.props);
      console.log('Next props: ', nextProps);
    }
    render() {
      // Wraps the input component in a container, without mutating it. Good!
      return <WrappedComponent {...this.props} />;
    }
  }
}

此外,如果您改变 HOC 内的 WrappedComponent,然后使用另一个 HOC 包装增强组件,则第一个 HOC 所做的更改将被覆盖。为了避免这种情况,您应该坚持组合组件而不是改变它们。

命名空间通用 Propnames

当您有多个堆叠时,命名空间道具名称的重要性是显而易见的。组件可能会将 prop 名称推送到已被另一个高阶组件使用的 WrappedComponent 中。

import React, { Component } from 'react';

const withMouse = (WrappedComponent) => {
  return class withMouse extends Component {
    constructor(props) {
      super(props);
      this.state = {
        name: 'Mouse'
      }
    }

    render() {

      return(
        <WrappedComponent {...this.props}  name={this.state.name} />
      );
    
    }
  }
}


const withCat = (WrappedComponent) => {
  return class withCat extends Component {

    render() {
      return(
        <WrappedComponent {...this.props} name= "Cat"  /> 
      )
    }
  }
}

const NameComponent = ({name}) => {
  
  return(
    <div> {name} </div>)
}


const App =() => {

  const EnhancedComponent  = withMouse(withCat(NameComponent));
  
  return(
  <div> <EnhancedComponent />  </div>)
}

export default App;

withMousewithCat 都在尝试推送自己的 name 版本。如果EnhancedComponent也必须共享一些同名的props怎么办?

<EnhancedComponent name="This is important" />

这不会给最终开发人员带来混乱和误导吗? React Devtools 不会报告任何名称冲突,您必须查看 HOC 实现细节才能了解出了什么问题。

这可以通过提供 HOC 属性名称的范围作为约定来解决。因此,您将拥有 withCat_namewithMouse_name 而不是通用的 prop 名称。

这里需要注意的另一件有趣的事情是,对属性进行排序在 React 中非常重要。当您多次拥有相同的属性并导致名称冲突时,最后一个声明将始终保留。在上面的例子中,Cat 获胜,因为它被放置在 { ...this.props } 之后。

如果您希望通过其他方式解决名称冲突,您可以重新排序属性并在最后传播 this.props 。这样,您就可以设置适合您的项目的合理默认值。

使用有意义的显示名称使调试更容易

由 HOC 创建的组件在 React Devtools 中显示为普通组件。很难区分两者。您可以通过为高阶组件提供有意义的 displayName 来简化调试。在 React Devtools 上拥有这样的东西不是明智的吗?

<withMouse(withCat(NameComponent)) > 
... 
</withMouse(withCat(NameComponent))>

那么 displayName 是什么?每个组件都有一个 displayName 属性,可用于调试目的。最流行的技术是包装 WrappedComponent 的显示名称。如果 withCat 是 HOC,并且 NameComponentWrappedComponent,则 displayName 将是 withCat(NameComponent).

const withMouse = (WrappedComponent) => {
  class withMouse extends Component {
    /*                       */   
 }

  withMouse.displayName = `withMouse(${getDisplayName(WrappedComponent)})`;
  return withMouse;
}

const withCat = (WrappedComponent) => {
  class withCat extends Component {
   /*                          */
  }

  withCat.displayName = `withCat(${getDisplayName(WrappedComponent)})`;
  return withCat;
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

高阶组件的替代方案

尽管 Mixins 已经消失,但说高阶组件是唯一允许代码共享和抽象的模式是有误导性的。另一种替代模式已经出现,我听说有人说它比 HOC 更好。深入探讨这个概念超出了本教程的范围,但我将向您介绍渲染道具和一些基本示例,以演示它们为何有用。

渲染道具有许多不同的名称:

  • 渲染道具
  • 儿童道具
  • 像孩子一样发挥作用
  • 渲染回调

这是一个简单的示例,应该解释渲染道具的工作原理。

class Mouse extends Component {

  constructor() {
    super();
    this.state = {
      name: "Nibbles"
    }
  }
  render() {
    return(
      <div>
        {this.props.children(this.state)}
      </div>
    )
  
  }
}

class App extends Component {
  render() {
    return(
      <Mouse>
        {(mouse) => <div> The name of the mouse is {mouse.name} </div> }
      </Mouse> 
      )
  }
}

如您所见,我们已经摆脱了高阶函数。我们有一个名为 Mouse 的常规组件。我们将渲染 this.props.children() 并将状态作为参数传递,而不是在其 render 方法中渲染包装的组件。所以我们给 Mouse 一个 render prop,而 render prop 决定应该渲染什么。

In other words, the Mouse component accepts a function as the value of the child property. When Mouse is rendered, it returns the state of Mouse and the render prop function can use it as it pleases.

What I like about this model:

  • From a readability perspective, the source of the props is more obvious.
  • This mode is dynamic and flexible. HOC is composed in static time. Although I've never found this to be a limitation, render props are dynamically combined and more flexible.
  • Simplified component composition. You can say goodbye to nesting multiple HOCs.

in conclusion

Higher-order components are a pattern that can be used to build robust, reusable components in React. If you are going to use a HOC, there are some basic rules you should follow. This is so that you don’t regret your decision to use them later. I've summarized most of the best practices in this tutorial.

HOC is not the only popular model today. At the end of this tutorial, I introduce you to another pattern called render props, which is becoming increasingly popular among React developers.

I will not judge one mode and say this mode is better than another mode. As React evolves and the ecosystem around it matures, more and more patterns will emerge. In my opinion, you should learn them all and stick to the one that suits your style and that you feel comfortable with.

This also marks the end of the high-level component tutorial series. We have started from scratch and mastered an advanced technology called HOC. If I missed anything or you have suggestions/ideas, I'd love to hear them. You can post them in the comments.

The above is the detailed content of Explore best practices for introducing higher-order components in React. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn