首页 男生 其他 深入浅出React和Redux

6.1.1 代理方式的高阶组件

深入浅出React和Redux 程墨 9523 2021-04-06 02:29

  您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!

  

  6.1.1 代理方式的高阶组件

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

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

  function removeUserProp(WrappedComponent) {

  return function newRender(props) {

  const {user, ...otherProps} = props;

  return

  }

  }

  虽然这样写代码更少,但是为了代码逻辑更加清晰,在本章其他的例子中,我们还是统一让高阶组件返回一个class,而不只是一个函数。

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

  ·操纵prop;

  ·访问ref;

  ·抽取状态;

  ·包装组件。

  下面分别介绍各种场景。

  1.操纵prop

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

  在render函数中,this.props包含新组件接收到的所有prop,最简单的方式当然是把this.props原封不动地传递给被包裹组件,当然,高阶组件也可以增减、删除、修改传递给包裹组件的props列表。

  上面的removeUserProp就是一个删除了特定prop的高阶组件,我们再看一个增加prop的例子:

  const addNewProps = (WrappedComponent, newProps) => {

  return class WrappingComponent extends React.Component {

  render() {

  return

  }

  }

  }

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

  const FooComponent = addNewPropsHOC(DemoComponent, {foo: 'foo'});

  const BarComponent = addNewPropsHOC(OtherComponent, {bar: 'bar'});

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

  2.访问ref

  在第4章介绍ref时我们就已经强调,访问ref并不是值得推荐的React组件使用方式,在这里我们只是展示应用高阶组件来实现这种方式的可行性。

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

  const refsHOC = (WrappedComponent) => {

  return class HOCComponent extends React.Component {

  constructor() {

  super(...arguments);

  this.linkRef = this.linkRef.bind(this);

  }

  linkRef(wrappedInstance) {

  this._root = wrappedInstance;

  }

  render() {

  const props = {...this.props, ref: this.linkRef};

  return ;

  }

  };

  };

  这个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函数执行的结果是另一个函数,这个函数才是高阶组件。

  在傻瓜组件和容器组件的关系中,通常让傻瓜组件不要管理自己的状态,只要做一个无状态的组件就好,所有状态的管理都交给外面的容器组件,这个模式就是“抽取状态”。

  我们尝试实现一个简易版的connect高阶组件,代码结构如下:

  const doNothing = () => ({});

  function connect(mapStateToProps=doNothing, mapDispatchToProps=doNothing) {

  return function(WrappedComponent) {

  class HOCComponent extends React.Component {

  //在这里定义HOCComponent的生命周期函数

  };

  HOCComponent.contextTypes = {

  store: React.PropTypes.object

  }

  return HOCComponent;

  };

  }

  创造的新组件HOCComponent需要利用React的Context功能,所以定义了context-Types属性,从Context中获取名为store的值。

  和react-redux中的connect方法一样,我们定义的connect方法接受两个参数,分别是mapStateToProps和mapDispatchToProps。返回的React组件类预期能够访问一个叫store的context值,在react-redux中,这个context由Provider提供,在组件中我们通过this.context.store就可以访问到Provider提供的store实例。

  我们在前面的章节中也实现了一个Provider的功能,在这里就不再赘述。

  为了实现类似react-redux的功能,HOCComponent组件需要一系列的成员函数来维持内部状态和store的同步,代码如下:

  constructor() {

  super(...arguments);

  this.onChange = this.onChange.bind(this);

  this.store = {};

  }

  componentDidMount() {

  this.context.store.subscribe(this.onChange);

  }

  componentWillUnmount() {

  this.context.store.unsubscribe(this.onChange);

  }

  onChange() {

  this.setState({});

  }

  在上面的代码中,借助store的subscribe和unsubscribe函数,HOCComponent保证每当Redux的Store上状态发生变化的时候,都会驱动这个组件的更新。

  虽然应该返回一个有状态的组件,但反正真正的状态存在Redux Store上,组件内的状态存什么真的不重要,我们使用组件状态的唯一原因是可以通过this.setState函数来驱动组件的更新过程,所以这个组件的状态实际上是一个空对象就足够。

  每当Redux Store上的状态发生变化时,就通过this.setState函数重设一遍组件的状态,每次都创造一个新的对象,虽然state没有任何有意义的值,却能够驱动组件的更新过程。

  再来看HOCComponent的render函数,代码如下:

  render() {

  const store = this.context.store;

  const newProps = {

  ...this.props,

  ...mapStateToProps(store.getState()),

  ...mapDispatchToProps(store.dispatch)

  }

  return ;

  }

  render中的逻辑类似“操纵Props”的方式,虽然渲染工作完全交给了Wrapped-Component,但是却控制住了传给WrappedComponent的props,因为WrappedComponent预期是一个无状态的组件,所以能够渲染什么完全由props决定。

  传递给connect函数的mapStateToProps和mapDispatchToProps两个参数在render函数中被使用,根据执行store.getState函数来得到Redux Store的状态,通过store.dispatch可以得到传递给mapDispatchToProps的dispatch方法。

  使用扩展操作符,this.props和mapStateToProps、mapDispatchToProps的返回结果被结合成一个对象,传递给了WrappedCompoent,就是最终的渲染结果。

  注意,这里我们实现的并不是完整的connect实现逻辑。比如,没有实现should-ComponentUpdate函数,缺少shouldComponentUpdate会导致每次Store状态变化都走一遍完整的更新过程,读者可以尝试着实现这个高阶组件中的shouldComponentUpdate函数。

  4.包装组件

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

  一个实用的例子是给组件添加样式style,代码如下:

  const styleHOC = (WrappedComponent, style) => {

  return class HOCComponent extends React.Component {

  render() {

  return (

  

  );

  }

  };

  };

  把一个组件用div包起来,并且添加一个style来定制其CSS属性,可以直接影响被包裹的组件对应DOM元素的展示样式。

  有了这个styleHOC,就可以给任何一个组件补充style的样式,代码如下:

  const style= {color: 'red'};

  const NewComponent = styleHOC(DemoComponent, style); 深入浅出React和Redux

目录
设置
手机
书架
书页
评论