对React中高阶组件的温和介绍:最佳实践

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

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

在本教程中,我们将介绍编写HOC时应注意的一些最佳做法和应做的事。

介绍

React以前有一个叫做React.createClass东西,它可以与React.createClass方法一起使用。 Mixins允许开发人员在组件之间共享代码。 但是,它们有一些缺点,并且最终放弃了这个想法。 没有将Mixins升级为支持ES6类,Dan Abramov甚至撰写了一篇深入的文章,介绍为什么将Mixins视为有害

高阶组件替代了Mixins,它们支持ES6类。 而且,HOC无需使用React API进行任何操作,并且是一种与React一起使用的通用模式。 但是,HOC也有缺陷。 尽管在较小的项目中高阶组件的缺点可能并不明显,但您可以将多个高阶组件链接到一个组件,如下所示。

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

您不应该让连锁问题直逼您问自己一个问题:“道具从何而来?” 本教程解决了高阶组件模式的一些常见问题以及正确解决这些问题的解决方案。

HOC的问题

与HOC有关的一些常见问题与HOC本身无关,而与您的实现有关。

如您所知,HOC非常适合抽象代码和创建可重用代码。 但是,当您堆叠了多个HOC时,如果某些东西看起来不合适或没有显示一些道具,则调试起来会很痛苦,因为React DevTools会为您提供关于可能出了什么问题的非常有限的线索。

现实世界中的HOC问题

为了了解HOC的缺点,我创建了一个示例演示,该示例嵌套了我们在上一教程中创建的一些HOC。 我们有四个包装该单个ContactList组件的高阶函数。 如果该代码没有意义,或者您没有遵循我的上一教程,那么这里是其工作原理的简要概述。

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

withAuth查找authentication道具,如果身份验证为true,则呈现WrappedComponent 。 如果认证为假,则将' /login '推送到历史对象。

除了WrappedComponent之外, withGenericContainer接受一个对象作为输入。 GenericContainer进行API调用并将结果存储在状态中,然后将数据作为道具发送到包装的组件。

withLoader是附加了加载指示器的HOC。 指示器旋转,直到获取的数据达到状态为止。

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} 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 ,决定忽略其所有道具。

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

这是一个非常常见的错误,在编写高阶组件时应尽量避免。 不熟悉HOC的人可能会发现很难弄清为什么所有道具都不见了,因为很难找出问题所在。 因此,始终记得在HOC中传播道具。

//The right way

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

不要传递没有HOC范围以外的其他内容的道具

HOC可能会引入WrappedComponent可能没有用的新道具。 在这种情况下,最好传递仅与组成部分相关的道具。

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

因为withGenericContainer是一个函数,所以您可以传入任意数量的参数。 在上面的示例中,配置对象用于指定组件的数据依赖性。 但是,增强组件和包装组件之间的合同严格通过道具实现。

因此,我建议通过函数参数填充静态数据依赖项,并将动态数据作为道具传递。 经过身份验证的道具是动态的,因为可以根据是否登录用户来对用户进行身份验证,但是我们可以确保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所做的更改将被覆盖。 为了避免这种情况,您应该坚持组成组件而不是对其进行变异。

命名空间通用属性名称

当您堆叠多个道具名称时,命名道具名称的重要性显而易见。 组件可能会将prop名称推入WrappedComponent中,而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 prop版本。 如果EnhancedComponent也必须共享一些同名道具,该怎么办?

<EnhancedComponent name="This is important" />

最终开发人员会不会引起混乱和误导? React Devtools不会报告任何名称冲突,您将必须调查HOC实施细节以了解出了什么问题。

可以通过提供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的常规组件。 与其在render方法中渲染包装的组件, this.props.children()在渲染this.props.children()并将状态作为参数传递。 因此,我们为Mouse了一个渲染道具 ,而渲染道具决定了应渲染的内容。

换句话说, Mouse组件接受一个功能作为儿童道具的值。 当Mouse渲染,它返回的状态Mouse和渲染道具功能,可以用它不过它为所欲为。

我喜欢这种模式的几件事:

  • 从可读性的角度来看,道具的来源更加明显。
  • 这种模式是动态的和灵活的。 HOC是在静态时间组成的。 尽管我从未发现这是一个限制,但渲染道具是动态组成的,并且更加灵活。
  • 简化的成分组成。 您可以告别嵌套多个HOC。

结论

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

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

我不会判断一种模式,说这种模式比另一种更好。 随着React的成长,以及围绕它的生态系统的成熟,将会出现越来越多的模式。 我认为,您应该学习所有这些内容,并坚持适合自己的风格并且对自己满意的一种。

这也标志着高阶组件教程系列的结束。 我们已经从零开始,掌握了一种称为HOC的先进技术。 如果我错过任何事情或您有建议/想法,我很想听听他们的意见。 您可以在评论中发布它们。

翻译自: https://code.tutsplus.com/tutorials/a-gentle-introduction-to-higher-order-components-in-react-part-3-best-practices--cms-30221

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值