- 高阶组件的概念及应用
- 以函数为子组件的模式 这两种方式的最终目的都是为了重用代码,只是策略不同,各有优劣,开发者可以在实际工作中决定采用哪种方式。
一、高阶组件 1. 高阶组件(Higher Order Component,HOC)并不是React提供的某种API,而是使用React的一种模式,用于增强现有组件的功能。一个高阶组件就是一个函数,这个函数接受一个组件作为输入,然后返回一个新的组件作为结果,而且,返回的新组件拥有了输入组件所不具备的功能。这里提到的组件并不是组件实例,而是组件类,也可以是一个无状态组件的函数。
-
高阶组件的意义: (1)重用代码。有时候很多React组件都需要公用同样一个逻辑,比如说React-Redux中容器组件的部分,没有必要让每个组件都实现一遍shouldComponentUpdate这些生命周期函数,把这部分逻辑提取出来,利用高阶组件的方式应用出去,就可以减少很多组件的重复代码。 **(2)修改现有React组件的行为。**有些现成的React组件并不是开发者自己开发的,来自于第三方,或者即便是我们自己开发的,但是我们不想去触碰这些组件的内部逻辑,这时候可以用高阶组件。通过一个独立于原有组件的函数,可以产生新的组件,对原有组件没有任何侵害。
-
根据返回的新组件和传入组件参数的关系,高阶组件的实现方式可以分为两大类:
- 代理方式的高阶组件
- 继承方式的高阶组件
二、代理方式的高阶组件
高阶组件的特点是返回的新组建类直接继承自React.Component类,新组件扮演的角色是传入参数组件的一个“代理”,在新组件的render函数中,把被包裹组件渲染出来,除了高阶组件自己要做的工作,其余功能全部转手给了被包裹的组件。如果高阶组件要做的功能不涉及除了render之外的生命周期函数,也不需要维护自己的状态,那也可以干脆返回一个纯函数,像上面的removeUserProp,代码如下:
代理方式的高阶组件,可以应用在下列场景中:
- 操纵prop
- 访问ref
- 抽取状态
- 包装组件
-
操纵prop 代理类型高阶组件返回的新组件,渲染过程也被新组件的render函数控制,而render函数相当于一个代理,完全决定如何使用被包裹的组件。在render函数中,this.props包含新组件接收到的所有prop,最简单的方式是把this.props原封不动的传递给被包裹的组件,当然,高阶组件也可以增减,删除,修改传递给包裹组件的props列表。 下面是一个增加props的例子:
这个高阶组件addNewProps增加了传递给包裹组件的prop,而不是删除prop,我们让addNewProps不只接受被包裹参数,还增加了一个new-Props参数,这样高阶组件的复用性更强,利用这样的高阶组件可以给不同的组件扩充新的属性,代码如下:
上面的代码中,新创造的FooComponent被添加了名为foo的prop,BarComponent被添加了名为bar的prop,除此之外,两者的功能也不一样,因为一个是通过Demo-Component产生,另一个是通过OtherComponent产生。由此可见,一个高阶组件可以重用在不同的组件上,减少代码的重复。
-
访问ref 访问ref并不是React组件推荐的使用方式,此处不做详细的介绍。
-
抽取状态 其实我们已经使用过”抽取状态“的高阶组件了,react-redux中的connect函数本身不是高阶组件,但是connect函数执行的结果是另一个函数,这个函数是高阶组件。 在傻瓜组件和容器组件的关系中,通常让傻瓜组件不要管理自己的状态,只要做一个无状态的组件就好,所有状态的管理都交给外面的容器组件,这个模式就是”抽取状态“。
-
包装组件 到目前为止,通过高阶组件产生的新组件,render函数都是直接返回被包裹组件,修改的只是props部分。其实render函数的JSX中完全可以引入其他的元素,甚至可以组合多个React组件。 一个实用的例子是给组件添加样式style,代码如下:
把一个组件用<div />包起来,并且添加一个style来定制其css属性,可以直接影响被包裹的组件对应DOM元素的展示样式。代码如下:
三、继承方式的高阶组件
继承方式的高阶组件采用继承关系关联作为参数的组件和返回的组件,假如传入的组件参数是WrappedComponent,那么返回的组件就直接继承自WrappedComponent。
我们用继承方式重新实现一遍removeUserprop这个组件,代码如下:
代理方式和继承方式最大的区别,是使用被包裹组件的方式。
在代理方式下,render函数中的使用被包裹组件是通过JSX代码:
return <WrappedComponent {...otherProps}>
在继承方式下,render函数中渲染被包裹组件的代码如下:
return super.render()
因为我们创造的新组件继承自传入的WrappedComponent,所以直接调用super.render就能够得到渲染出来的元素。
注意:在代理方式下,WrappedComponent经历了一个完整的生命周期,但在继承方式下super.render只是一个生命周期中的一个函数而已,在代理方式下产生的新组件和参数组件是两个不同的组件,一次渲染,两个组件都要经历各自的生命周期,在继承方式下两者合二为一,只有一个生命周期。
上面的例子直接修改了this.props,这种做法是不推荐的!
继承方式的高阶组件可以应用于下列场景:
- 操纵prop
- 操纵生命周期函数
- 操纵props
- 操纵生命周期函数
四、以函数为子组件
-
高阶组件的缺点:对原有组件的props有了固化的要求。也就是说,能不能把一个高阶组件作用于一个组件X,要先看一下这个组件X是不是能够接受高阶组件传过来的props,如果组件X并不支持这些props,或者对这些props命名有不同,或者使用方式不是预期的方式,那也就没有办法应用这个高阶组件。 “以函数为子组件”的模式就是为了克服高阶组件的这种局限而生的。在这种模式下,实现代码重用的不是一个函数,而是一个真正的React组件,这样的React组件有个特点,要求必须有子组件的存在,而且这个子组件必须是一个函数。在组件实例的生命周期函数中,this.props.children引用的就是子组件,render函数会直接把this.props.children当做函数来调用,得到的结果就可以作为render返回结果的一部分。 代码如下:
AddUserProp首字母大写,因为现在创造的是一个真正的组件类,而不是一个函数,这个类的代码,和被增强组件的唯一联系就是this.props.children,而且this.props.children是函数类型,在render函数中直接调用this.props.children,参数就是我们希望传递下去的user。 使用这个AddUserProp的灵活之处在于它没有对被增强组件有任何props要求,只是传递一个参数过去,至于怎么使用,完全由作为子组件的函数决定。 对于AddUserProp的使用,如下:
作为AddUserProp子组件的函数,成为了连接父组件和底层组件的桥梁。一个函数可以包含各种逻辑,这样就给使用AddUserProp提供了最大的灵活性。“以函数为子组件”的模式没有高阶组件那么多分类和应用场景,因为以函数为连接桥梁的方式已经提供了无数中用例。
-
性能优化问题 (1)使用shouldComponentUpdate进行性能优化:
(2)不适用匿名函数