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

前端之家收集整理的这篇文章主要介绍了React学习之进阶终临高阶组件(二十一)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

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

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

  1. const EnhancedComponent = higherOrderComponent(WrappedComponent);

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

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

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

1.构造联系

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

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

如下:

  1. class CommentList extends React.Component {
  2. constructor() {
  3. super();
  4. this.handleChange = this.handleChange.bind(this);
  5. this.state = {
  6. // "DataSource"是一个全局的数据处理类,用来处理数据的,可以不要在意其中的细节
  7. comments: DataSource.getComments()
  8. };
  9. }
  10.  
  11. componentDidMount() {
  12. //监听数据变化
  13. DataSource.addChangeListener(this.handleChange);
  14. }
  15.  
  16. componentWillUnmount() {
  17. // 清除监听事件
  18. DataSource.removeChangeListener(this.handleChange);
  19. }
  20.  
  21. handleChange() {
  22. // 当数据发生变化时触发事件
  23. this.setState({
  24. comments: DataSource.getComments()
  25. });
  26. }
  27.  
  28. render() {
  29. return (
  30. <div>
  31. {this.state.comments.map((comment) => (
  32. <Comment comment={comment} key={comment.id} />
  33. ))}
  34. </div>
  35. );
  36. }
  37. }

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

  1. class BlogPost extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.handleChange = this.handleChange.bind(this);
  5. this.state = {
  6. blogPost: DataSource.getBlogPost(props.id)
  7. };
  8. }
  9.  
  10. componentDidMount() {
  11. DataSource.addChangeListener(this.handleChange);
  12. }
  13.  
  14. componentWillUnmount() {
  15. DataSource.removeChangeListener(this.handleChange);
  16. }
  17.  
  18. handleChange() {
  19. this.setState({
  20. blogPost: DataSource.getBlogPost(this.props.id)
  21. });
  22. }
  23.  
  24. render() {
  25. return <TextBlock text={this.state.blogPost} />;
  26. }
  27. }

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

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

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

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

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

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

如下:

  1. const CommentListWithSubscription = withSubscription(
  2. CommentList,(DataSource) => DataSource.getComments()
  3. );
  4.  
  5. const BlogPostWithSubscription = withSubscription(
  6. BlogPost,(DataSource,props) => DataSource.getBlogPost(props.id)
  7. });

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

  1. // This function takes a component...
  2. function withSubscription(WrappedComponent,selectData) {
  3. //返回一个新构建的组件
  4. return class extends React.Component {
  5. constructor(props) {
  6. super(props);
  7. this.handleChange = this.handleChange.bind(this);
  8. this.state = {
  9. data: selectData(DataSource,props)
  10. };
  11. }
  12.  
  13. componentDidMount() {
  14. //进行事件绑定
  15. DataSource.addChangeListener(this.handleChange);
  16. }
  17.  
  18. componentWillUnmount() {
  19. DataSource.removeChangeListener(this.handleChange);
  20. }
  21.  
  22. handleChange() {
  23. this.setState({
  24. data: selectData(DataSource,this.props)
  25. });
  26. }
  27.  
  28. render() {
  29. return <WrappedComponent data={this.state.data} {...this.props} />;
  30. }
  31. };
  32. }

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

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

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

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

HOC中拒绝去修改原始数据

  1. function logProps(InputComponent) {
  2. InputComponent.prototype.componentWillReceiveProps(nextProps) {
  3. console.log('Current props: ',this.props);
  4. console.log('Next props: ',nextProps);
  5. }
  6. // 对原组件进行的扩展,修改了原组件
  7. return InputComponent;
  8. }
  9.  
  10.  
  11. const EnhancedComponent = logProps(InputComponent);

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

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

  1. function logProps(WrappedComponent) {
  2. return class extends React.Component {
  3. componentWillReceiveProps(nextProps) {
  4. console.log('Current props: ',this.props);
  5. console.log('Next props: ',nextProps);
  6. }
  7. render() {
  8.  
  9. return <WrappedComponent {...this.props} />;
  10. }
  11. }
  12. }

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

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

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

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

  1. render() {
  2. // 分离出额外的属性
  3. const { extraProp,...passThroughProps } = this.props;
  4.  
  5. // 执行的函数
  6. const injectedProp = someStateOrInstanceMethod;
  7.  
  8. // 包裹组件
  9. return (
  10. <WrappedComponent
  11. injectedProp={injectedProp}
  12. {...passThroughProps}
  13. />
  14. );
  15. }

4.最大化可组合性

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

  1. const NavbarWithRouter = withRouter(Navbar);

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

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

又或者是Reduxconnect函数

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

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

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

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

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

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

下一篇将讲React的高级运用

猜你在找的React相关文章