• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

React中高阶组件的简要介绍:最佳实践

这是高阶组件系列的第三部分。在第一个教程中,我们从零开始。我们学习了 ES6 语法、高阶函数和高阶组件的基础知识。 

高阶组件模式对于创建抽象组件很有用——您可以使用它们与现有组件共享数据(状态和行为)。在本系列的第二部分中,我演示了使用此模式的实际代码示例。这包括受保护的路由、创建可配置的通用容器、将加载指示器附加到组件等。 

在本教程中,我们将了解一些最佳实践以及在编写 HOC 时应该注意的注意事项。 

介绍

react 之前有一个叫做 Mixins 的东西,它在这个 React.createClass方法上效果很好。Mixins 允许开发人员在组件之间共享代码。然而,他们有一些缺点,最终放弃了这个想法Mixins 没有升级为支持 ES6 类,Dan Ab ram ov 甚至写了一篇关于为什么 认为 Mixins 有害的文章。

高阶组件作为 Mixins 的替代品出现,它们支持 ES6 类。此外,HOC 不必对 React api做任何事情,并且是一种适用于 React 的通用模式。然而,HOC 也有缺陷尽管高阶组件的缺点在较小的项目中可能并不明显,但您可以将多个高阶组件链接到单个组件,如下所示。

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

你不应该让链接达到你问自己这个问题的地步:“那些道具是从哪里来的?” 本教程解决了高阶组件模式的一些常见问题以及使它们正确的解决方案。 

HOC的问题

与 HOC 有关的一些常见问题与 HOC 本身关系不大,而与您对它们的实现有关。 

如您所知,HOC 非常适合代码抽象和创建可重用代码。然而,当你有多个 HOC 堆叠在一起时,如果某些东西看起来不合适或者某些 props 没有显示出来,调试起来会很痛苦,因为 React DevTools 给你的线索非常有限,可能会出错。 

现实世界的 HOC 问题

为了了解 HOC 的缺点,我创建了一个示例演示,其中嵌套了我们在上一教程中创建的一些 HOC。我们有四个高阶函数包装了单个 ContactList 组件。如果代码没有意义,或者您没有按照我之前的教程进行操作,这里是其工作原理的简要总结。

withRouter是一个 HOC,它是 react-router 包的一部分。它使您可以访问历史对象的属性,然后将它们作为道具传递。 

withAuth寻找一个authentication道具,如果身份验证为真,它会呈现WrappedComponent如果身份验证为假,则将“”推/login送到历史对象。

withGenericContainer除了 . 之外,还接受一个对象作为输入WrappedComponentGenericContainer进行 API 调用并将结果存储在 state 中,然后将数据作为 props 发送到包装的组件

withLoader是一个附加了加载指示器的 HOC。指示器旋转,直到fetch ed 数据达到状态。

最佳实践演示。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} width="100px" height="100px"  alt="presentation" />
          <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;

现在您可以亲眼看到高阶组件的一些常见缺陷。让我们详细讨论其中的一些。

基本注意事项

不要忘记在你的 HOC 中传播道具

假设我们在组合层次结构authenticated = { this.state.authenticated }的顶部有一个道具我们知道这是一个重要的道具,它应该一直用到展示组件。然而,想象一个中间的 HOC,比如withGenericContainer,决定忽略它的所有 props。 

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

这是一个非常常见的错误,在编写高阶组件时应该尽量避免。不熟悉 HOC 的人可能会发现很难弄清楚为什么所有道具都丢失了,因为很难隔离问题。所以,永远记得在你的 HOC 中传播道具。

//The right way

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

不要传递超出 HOC 范围的不存在的道具

HOC 可能会引入 WrappedComponent 可能没有任何用处的新道具。在这种情况下,最好传递仅与组合组件相关的道具。 

高阶组件可以通过两种方式接受数据:作为函数的参数或作为组件的道具。例如,authenticated = { this.state.authenticated }是一个道具的例子,而在 中 withGenericContainer(reqAPI)(ContactList),我们将数据作为参数传递。  

因为 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 内部改变 Wrapped Component 使得在 HOC 外部使用 Wrapped Component 成为不可能。如果你的 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 所做的更改将被覆盖。为了避免这种场景ios,你应该坚持组合组件而不是改变它们。

命名空间通用属性名

当您有多个堆叠时,命名空间道具名称的重要性显而易见。一个组件可能会将一个 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在尝试推出他们自己版本的名称道具。如果 EnhancedComponent 也必须共享一些具有相同名称的道具怎么办?

<EnhancedComponent name="This is important" />

对于最终开发人员来说,这不会是混乱和误导的根源吗?React Devtools 不会报告任何名称冲突,您必须查看 HOC 实现细节以了解问题所在。 

这可以通过提供它们的 HOC 将 HOC 属性名称限定为约定范围来解决。所以你会有withCat_nameandwithMouse_name而不是一个通用的道具名称。 

这里要注意的另一件有趣的事情是,在React中对属性进行排序很重要。当您多次拥有相同的属性,导致名称冲突时,最后一个声明将始终存在。在上面的例子中,猫获胜,因为它被放置在 之后{ ...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';
}

高阶组件的替代方案

尽管 Mixin 已经消失,但如果说高阶组件是唯一允许代码共享和抽象的模式,那将是一种误导。另一种替代模式已经出现,我听说有人说它比 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()并传入状态作为参数,而不是在其渲染方法中渲染包装的组件。所以我们给出Mouse了一个渲染道具,渲染道具决定应该渲染什么。

换句话说,Mouse组件接受一个函数作为子属性的值。渲染时Mouse,它会返回 的状态Mouse,并且 render prop 函数可以随心所欲地使用它。 

我喜欢这种模式的几点:

  • 从可读性的角度来看,道具的来源更加明显。

  • 这种模式是动态的和灵活的。HOC 是在静态时间组成的。虽然我从来没有发现这是一个限制,但渲染道具是动态组合的并且更灵活。 

  • 简化的组件组成。您可以告别嵌套多个 HOC。

结论

高阶组件是可用于在 React 中构建健壮、可重用组件的模式。如果您要使用 HOC,则应遵循一些基本规则。这样您就不会后悔以后使用它们的决定。我总结了本教程中的大部分最佳实践。 

HOC 并不是当今唯一流行的模式。在本教程即将结束时,我向您介绍了另一种称为渲染道具的模式,该模式在 React 开发人员中越来越受欢迎。 

我不会判断一种模式并说这个模式比另一个好。随着 React 的成长,以及围绕它的生态系统的成熟,越来越多的模式将会出现。在我看来,您应该全部学习它们并坚持使用适合您的风格并且您感到舒适的一种。

React中高阶组件的简要介绍:最佳实践  第1张

文章目录
  • 介绍
  • HOC的问题
    • 现实世界的 HOC 问题
      • 最佳实践演示。jsx
  • 基本注意事项
    • 不要忘记在你的 HOC 中传播道具
    • 不要传递超出 HOC 范围的不存在的道具
    • 不要在渲染方法中使用 HOC
    • 不要改变被包裹的组件
    • 命名空间通用属性名
    • 使用有意义的显示名称使调试更容易
  • 高阶组件的替代方案
  • 结论