React学习之进阶终临高阶组件(二十一)

HOC高级组件在React是一种比较先进的重用组件的方法,高级组件不是ReactAPI,它是一种从React的组件方式中合并而成的一种模式,所以可以说HOC不是组件,而是一个处理模式

准确地来说,HOC组件是一个处理组件并且返回新组件的函数,但是这个函数又是一个纯函数,遵循函数式编程规范。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

就是说,HOC就是将一个组件变为另一个组件。

HOC在第三方React库中是非常普遍的,比如Reduxconnect和Relay中的createContainer,这两个函数后面会提到一点他们的大概实现方式和使用形式。

接下来,将讨论的是HOC的用处和怎么去实现它

1.构造联系

之前我提到的mixins就是一种构造父子关系方式,但是mixins会存在一定程度坏处。所以我们有必要用另外一种方式来处理这种关系。

组件是React复用代码的基本单位,然而,你会发现一些实现的方式并不适合传统的组件。

如下:

class CommentList extends React.Component {
  constructor() {
    super();
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      // "DataSource"是一个全局的数据处理类,用来处理数据的,可以不要在意其中的细节
      comments: DataSource.getComments()
    };
  }

  componentDidMount() {
    //监听数据变化
    DataSource.addChangeListener(this.handleChange);
  }

  componentWillUnmount() {
    // 清除监听事件
    DataSource.removeChangeListener(this.handleChange);
  }

  handleChange() {
    // 当数据发生变化时触发事件
    this.setState({
      comments: DataSource.getComments()
    });
  }

  render() {
    return (
      <div>
        {this.state.comments.map((comment) => (
          <Comment comment={comment} key={comment.id} />
        ))}
      </div>
    );
  }
}

这其中的DataSource用到了观察者模式,在组件中进行事件的订阅。我们再看下面这份代码

class BlogPost extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {
      blogPost: DataSource.getBlogPost(props.id)
    };
  }

  componentDidMount() {
    DataSource.addChangeListener(this.handleChange);
  }

  componentWillUnmount() {
    DataSource.removeChangeListener(this.handleChange);
  }

  handleChange() {
    this.setState({
      blogPost: DataSource.getBlogPost(this.props.id)
    });
  }

  render() {
    return <TextBlock text={this.state.blogPost} />;
  }
}

两份代码相似但是有不同,他们最终渲染出的结果是不同的,但是有三点是相同的

  1. 在挂载时,给DataSource监听了一个事件

  2. 在事件内部,如果DataSource内部的数据发生变化的时候,会调用setState来进行更新

  3. 在卸载时,会移除掉监听在DataSource上的事件

可以想象的是,当我们构建大型的应用程序的时候,通过订阅DataSource然后通过setState去更新状态,这些步骤我们都可以抽象出来,形成一个函数,然后在多个组件中分享使用,这就是高级组件的用法。

我们可以写一个创建组件的函数,像CommentList和blogPost那样去订阅DataSource。这个函数接受一个组件作为参数来监听事件。

如下:

const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) => DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id)
});

其中第一个参数是一个组件,第二参数则是我们在DataSource数据进行改变时需要执行的操作。

// This function takes a component...
function withSubscription(WrappedComponent, selectData) {
//返回一个新构建的组件
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.state = {
        data: selectData(DataSource, props)
      };
    }

    componentDidMount() {
      //进行事件绑定
      DataSource.addChangeListener(this.handleChange);
    }

    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);
    }

    handleChange() {
      this.setState({
        data: selectData(DataSource, this.props)
      });
    }

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

这个HOC不会对输入的组件做任何修改,也不做任何继承赋值的行为,它就相当于是在原来的组件的基础上进行扩展,这就是用函数式编程的好处,一般都说是纯函数零副作用。

HOC并不知道你干了什么,它只是调用你想要调用的回调函数,然后进行数据处理,没有直接跟数据进行交互处理,它根本不担心你干了什么,你的数据从哪里来。

widthSubscription大体上看,这是一个普通的不能再普通的函数,我们可以自行的为这函数进行扩充,比如:为内部存取的数据,换一个不名字,不用data而用自己特定的,又或是,可以增加shouldComponentUpdate来实现对一些组件不执行更新等等,都是可以的。

2.不要试图修改原始的组件,使用组合方法

HOC中拒绝去修改原始数据

function logProps(InputComponent) {
  InputComponent.prototype.componentWillReceiveProps(nextProps) {
    console.log('Current props: ', this.props);
    console.log('Next props: ', nextProps);
  }
  // 对原组件进行的扩展,修改了原组件
  return InputComponent;
}


const EnhancedComponent = logProps(InputComponent);

上面的代码就存在几个非常明显的问题了,一个就是原组件在函数中进行改变然后返回,更关键的就是,如果我们的组件中已经写好了一个componentWillReceiveProps函数处理,但是在logProps会被覆盖掉,很明显这不是我们想要的。

所以要避免这种事情,我们就可以采用上面已经提到的包容组件的方式,就是重新创建一个新的组件,让这个组件包裹传递进来的组件,而传递进来的组件则在render中渲染出去。

function logProps(WrappedComponent) {
  return class extends React.Component {
    componentWillReceiveProps(nextProps) {
      console.log('Current props: ', this.props);
      console.log('Next props: ', nextProps);
    }
    render() {

      return <WrappedComponent {...this.props} />;
    }
  }
}

上面的代码就变得非常正确了。

3.通过包容组件来处理props不相关

就之前的说到的东西,我们可以知道,HOC模式给组件进行扩展的方式是,通过一个临时的组件将该组件包括起来然后返回出去

包容组件和被包容的组件通过一个props来进行传递,这一步通常是在render中。

render() {
  // 分离出额外的属性
  const { extraProp, ...passThroughProps } = this.props;

  // 执行的函数
  const injectedProp = someStateOrInstanceMethod;

  // 包裹组件
  return (
    <WrappedComponent
      injectedProp={injectedProp}
      {...passThroughProps}
    />
  );
}

4.最大化可组合性

不是所有的HOC都会接受一个回调函数进行处理,有时只有一个参数,就是要被包裹的组件

const NavbarWithRouter = withRouter(Navbar);

又或者是如同Relay.createContainer用一个config来进行配置

const CommentWithRelay = Relay.createContainer(Comment, config);

又或者是Reduxconnect函数

const ConnectedComment = connect(commentSelector, commentActions)(Comment);

如果上面看不懂,可以看下面的分步:

const enhance = connect(commentListSelector, commentListActions);
const ConnectedComment = enhance(CommentList);

connect就是一个高阶函数,返回的就是一个高阶组件,但是这也许让人疑惑,但是这是一个好方式,通过调用不断将需要的参数传递进去,内部是函数式编程规范进行处理的。

建议大家可以好好的学习一下函数式编程。

这里还有很多实用方法,之后会更新,个人还不是特别会,需要不断实践,才能更新。

下一篇将讲React的高级运用

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值