react之代理方式的高阶组件(一)

本文介绍了React中的高阶组件(HOC)概念,它是一种代码复用和组件功能增强的模式,而非React API。高阶组件通过函数接收一个组件并返回新的组件,可用于操纵prop、访问ref、抽取状态和包装组件等场景。文中举例展示了如何创建和使用高阶组件,强调了其在减少重复代码和修改组件行为方面的作用。
摘要由CSDN通过智能技术生成

当我们开发出更多React组件时,就会遇到这样的问题,多个组件都需要某个功能,而且这个功能和界面并没有关系,所以也不能简单地抽取成一个新的组件,但是如果让同样的逻辑在各个组件里各自实现,无疑会导致重复代码。“重复是优秀系统设计的大敌。”——Robert C.Martin

构建更易于复用、更灵活的React高级组件,包含下面两种方式:

  • 高阶组件的概念及应用
  • 以函数为子组件的模式

这两种方式的最终目的都是为了重用代码,只是策略不同,各有优劣,在实际工作中要根据实际情况决定采用何种方式。

高阶组件(Higher Order Component, HOC)并不是React提供的某种API,而是使用React的一种模式,用于增强现有组件的功能。

简单来说,一个高阶组件就是一个函数,这个函数接受一个组件作为输入,然后返回一个新的组件作为结果,而且,返回的新组件拥有了输入组件所不具有的功能。

这里提到的组件指的并不是组件实例,而是一个组件类,也可以是一个无状态组件的函数。

提示:在有的文献中,认为上面提到的函数返回的结果才应该叫“高阶组件”,而这个函数本身应该叫做“高阶组件工厂函数”,这样的定义更加严谨。但是在本书中,我们遵循更普遍的定义,把增强功能的函数叫做“高阶组件”,读者只需要知道存在这两种说法就可以。

我们先看一个非常简单的高阶组件的例子,感受一下高阶组件是如何工作的,代码如下:

定义了一个函数removeUserProp,这样一个高阶组件做的工作非常简单,它有一个名叫WrappedComponent的参数,代表一个组件类,这个函数返回一个新的组件,所做的事情和WrappedComponent一模一样,只是忽略名为user的prop。也就是说,如果WrappedComponent能够处理名为user的prop,这个高阶组件返回的组件则完全无视这个prop。

函数removeUserProp直接返回了一个class的定义,这个class名叫WrappingCom-ponent。其实这个class叫什么名字并不重要,因为它只是一个局部变量,这个Wrapping-Component继承自React.Component,表明返回的结果依然是一个组件类,像上面说过的,高阶组件就是根据一个组件类产生一个新的组件类。WrappingComponent作为一个React组件,必须要有自己的render函数,在它的render函数中,利用扩展操作符将this.props分为两部分:user,以及不是user的所有其他props,利用ES6语法的技巧,可以简洁地把一个对象中特定字段过滤掉,结果赋值给一个新的对象。经过上面的语句,otherProps里面就有this.props中所有的字段,除了user。然后,render函数把渲染的工作完全交给了removeUserProp函数的参数,只是使用了过滤掉了user的otherProps,把这个WrappedComponent组件渲染的结果作为render函数返回的结果。

假如我们现在不希望某个组件接收到user的prop,那么我们就不要直接使用这个组件,而是把这个组件作为参数传递给removeUserProp函数,然后我们把这个函数的返回结果当做组件来使用

在上面的代码中,NewComponent拥有和SampleComponent完全一样的行为,唯一的区别就是即使传递user属性给它,它也会当没有user来处理。当然,这看起来是削弱SampleComponent的功能,而不是“增强”其功能,不过,只要把“屏蔽某个prop”也看做一种功能增强,那这个高阶组件已经完成了“增强”的工作。定义高阶组件的意义何在呢?首先,重用代码。有时候很多React组件都需要公用同样一个逻辑,比如说react-redux中容器组件的部分,没有必要让每个组件都实现一遍shouldComponentUpdate这些生命周期函数,把这部分逻辑提取出来,利用高阶组件的方式应用出去,就可以减少很多组件的重复代码。其次,修改现有React组件的行为。有些现成React组件并不是开发者自己开发的,来自于第三方,或者,即使是我们自己开发的,但是我们不想去触碰这些组件的内部逻辑,这时候高阶组件有了用武之地。通过一个独立于原有组件的函数,可以产生新的组件,对原有组件没有任何侵害。

根据返回的新组件和传入组件参数的关系,高阶组件的实现方式可以分为两大类:

  •  代理方式的高阶组件;
  •  继承方式的高阶组件。

1、代理方式的高阶组件

上面的removeUserProp例子就是一个代理方式的高阶组件,特点是返回的新组件类直接继承自React.Component类。新组件扮演的角色是传入参数组件的一个“代理”,在新组建的render函数中,把被包裹组件渲染出来,除了高阶组件自己要做的工作,其余功能全都转手给了被包裹的组件。

如果高阶组件要做的功能不涉及除了render之外的生命周期函数,也不需要维护自己的状态,那也可以干脆返回一个纯函数,像上面的removeUserProp,代码可以简写成下面这样

代理方式的高阶组件,可以应用在下列场景中:

  1. 操纵prop;
  2. 访问ref;
  3. 抽取状态;
  4. 包装组件。

1.操纵prop

代理类型高阶组件返回的新组件,渲染过程也被新组件的render函数控制,而render函数相当于一个代理,完全决定如何使用被包裹的组件。

在render函数中,this.props包含新组件接收到的所有prop,最简单的方式当然是把this.props原封不动地传递给被包裹组件,当然,高阶组件也可以增减、删除、修改传递给包裹组件的props列表。上面的removeUserProp就是一个删除了特定prop的高阶组件,我们再看一个增加prop的例子

和removeUserProp相反,这个高阶组件addNewProps增加了传递给被包裹组件的prop,而不是删除prop,我们让addNewProps不只接受被包裹参数,还增加了一个new-Props参数,这样高阶组件的复用性更强,利用这样一个高阶组件可以给不同的组件扩充不同的新属性,代码如下:

在上面的代码中,新创造的FooComponent被添加了名为foo的prop, BarComponent被添加了名为bar的prop。除此之外,两者的功能也不一样,因为一个是通过Demo-Component产生,另一个是根据OtherComponent产生。由此可见,一个高阶组件可以在重用在不同的组件上,减少代码的重复。

2.   访问ref

我们写一个名为refsHOC的高阶组件,能够获得被包裹组件的直接应用ref,然后就可以根据ref直接操纵被包裹组件的实例,代码如下:

这个refsHOC的工作原理其实也是增加传递给被包裹组件的props,只是利用了ref这个特殊的prop,ref这个prop可以是一个函数,在被包裹组件的装载过程完成的时候被调用,参数就是被装载的组件本身。传递给被包裹组件的ref值是一个成员函数linkRef,当linkRef被调用时就得到了被包裹组件的DOM实例,记录在this._root中。这样的高阶组件有什么作用呢?可以说非常有用,也可以说没什么用。说它非常有用,是因为只要获得了对被包裹组件的ref引用,那它基本上就无所不能,因为通过这个引用可以任意操纵一个组件的DOM元素。说它没什么用,是因为ref的使用非常容易出问题,我们已经知道最好能用“控制中的组件”(Controlled Component)来代替ref。软件开发中一个问题可以有很多解法,可行的解法很多,但是合适的解法不多,了解一种可能性并不表示一定要使用它,我们只需要知道高阶组件有访问ref这种可能,并不意味着我们必须要使用这种高阶组件。

3.抽取状态

其实,我们已经使用过“抽取状态”的高阶组件了,就是react-redux的connect函数,注意connect函数本身并不是高阶组件,connect函数执行的结果是另一个函数,这个函数才是高阶组件。在傻瓜组件和容器组件的关系中,通常让傻瓜组件不要管理自己的状态,只要做一个无状态的组件就好,所有状态的管理都交给外面的容器组件,这个模式就是“抽取状态”。

4.包装组件

到目前为止,通过高阶组件产生的新组件,render函数都是直接返回被包裹组件,修改的只是props部分。其实render函数的JSX中完全可以引入其他的元素,甚至可以组合多个React组件,这样就会得到更加丰富多彩的行为。一个实用的例子是给组件添加样式style,代码如下:

把一个组件用div包起来,并且添加一个style来定制其CSS属性,可以直接影响被包裹的组件对应DOM元素的展示样式。有了这个styleHOC,就可以给任何一个组件补充style的样式,代码如下:

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值