美文网首页
高阶组件(HOC)

高阶组件(HOC)

作者: hanxianshe_9530 | 来源:发表于2019-10-25 11:07 被阅读0次

    React官方定义高阶组件的概念是:

    A higher-order component is a function that takes a component and returns a new component.

    通常情况下,实现高阶组件的方式有以下两种:
    属性代理(Props Proxy)
    反向继承(Inheritance Inversion)

    属性代理

    实质上是通过包裹原来的组件来操作props,举个简单的例子:

    import React, { Component } from 'React';
    //高阶组件定义
    const HOC = (WrappedComponent) =>
      class WrapperComponent extends Component {
        render() {
          return <WrappedComponent {...this.props} />;
        }
    }
    //普通的组件
    class WrappedComponent extends Component{
        render(){
            //....
        }
    }
    
    //高阶组件使用
    export default HOC(WrappedComponent)
    

    我们可以看见函数HOC返回了新的组件(WrapperComponent),这个组件原封不动的返回作为参数的组件(也就是被包裹的组件:WrappedComponent),并将传给它的参数(props)全部传递给被包裹的组件(WrappedComponent)。这么看起来好像并没有什么作用,其实属性代理的作用还是非常强大的。

    操作props

    我们看到之前要传递给被包裹组件WrappedComponent的属性首先传递给了高阶组件返回的组件(WrapperComponent),这样我们就获得了props的控制权(这也就是为什么这种方法叫做属性代理)。我们可以按照需要对传入的props进行增加、删除、修改(当然修改带来的风险需要你自己来控制),举个例子:

    const HOC = (WrappedComponent) =>
        class WrapperComponent extends Component {
            render() {
                const newProps = {
                    name: 'HOC'
                }
                return <WrappedComponent
                    {...this.props}
                    {...newProps}
                />;
            }
        }
    

    在上面的例子中,我们为被包裹组件(WrappedComponent)新增加了固定的name属性,因此WrappedComponent组件中就会多一个name的属性。

    抽象state

    属性代理的情况下,我们可以将被包裹组件(WrappedComponent)中的状态提到包裹组件中,一个常见的例子就是实现不受控组件到受控的组件的转变

    class WrappedComponent extends Component {
        render() {
            return <input name="name" {...this.props.name} />;
        }
    }
    
    const HOC = (WrappedComponent) =>
        class extends Component {
            constructor(props) {
                super(props);
                this.state = {
                    name: '',
                };
    
                this.onNameChange = this.onNameChange.bind(this);
            }
    
            onNameChange(event) {
                this.setState({
                    name: event.target.value,
                })
            }
    
            render() {
                const newProps = {
                    name: {
                        value: this.state.name,
                        onChange: this.onNameChange,
                    },
                }
                return <WrappedComponent {...this.props} {...newProps} />;
            }
        }
    
    

    上面的例子中通过高阶组件,我们将不受控组件(WrappedComponent)成功的转变为受控组件.

    用其他元素包裹组件

        render(){
            <div>
                <WrappedComponent {...this.props} />
            </div>
        }
    

    这种方式将被包裹组件包裹起来,来实现布局或者是样式的目的。

    在属性代理这种方式实现的高阶组件,以上述为例,组件的渲染顺序是: 先WrappedComponent再WrapperComponent(执行ComponentDidMount的时间)。而卸载的顺序是先WrapperComponent再WrappedComponent(执行ComponentWillUnmount的时间)。

    高阶组件的用法,其实就是封装个函数将传入的组件添加上数据,直接导出即可,我们常用的react-redux 中的 connect(Children) 一个道理,封装完将数据导入到组件当中,组件相应的具有数据,以及具有了dispatch方法,就是这么个封装。
    话不多说直接上个小栗子:

    class Parents extends Component {
      constructor(props) {
        super(props);
          this.state = {
             parentsSourse: '我是父组件数据'
          }
      }
      render() {
        <>
          <Children />
          这是父组件,相当于我们的外层组件
        </>  
      }    
    }    
    class Children  extends Component {
       render() {
          <>
             这是子组件,我们展示组件
          </>  
       }    
    }
    

    我们假如我们想让父组件包含的组件都具有一个属性值,这个值是 newType: true, 此时我们可以直接向下级 Childlren 传递,那么我们也可以封装下父组件导出个高阶组件,那么这个方法可以这么写:

    const Hoc_component = (HocCompoent) =>  {
       return  class NewComponent extends React.Component{
          constructor(props){
             super(props);
             this.state={}
          }
        
           render() {
                const  props = { newType: true } 
                return <HocCompoent {...this.props}  {...props}/>
           }
       }
    }    
    
    // 此时所有的组件只要使用
    
    Hoc_component(Children);   // 此时的子组件就具有了这个方法包装的 newType属性,我们可以去打印看下。
    

    下面的例子,我们把两个组件相似的生命周期方法提取出来,通过包装,能够节省非常多的重复代码。

    // CommentList
    class CommentList extends React.Component {
      constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.state = {
          // "DataSource" is some global data source
          comments: DataSource.getComments()
        };
      }
    
      componentDidMount() {
        // Subscribe to changes
        DataSource.addChangeListener(this.handleChange);
      }
    
      componentWillUnmount() {
        // Clean up listener
        DataSource.removeChangeListener(this.handleChange);
      }
    
      handleChange() {
        // Update component state whenever the data source changes
        this.setState({
          comments: DataSource.getComments()
        });
      }
    
      render() {
        return (
          <div>
            {this.state.comments.map((comment) => (
              <Comment comment={comment} key={comment.id} />
            ))}
          </div>
        );
      }
    }
    
    // BlogPost
    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} />;
      }
    }
    

    他们虽然是两个不同的组件,对DataSource的需求也不同,但是他们有很多的内容是相似的:

    • 在组件渲染之后监听DataSource
    • 在监听器里面调用setState
    • 在unmout的时候删除监听器

    在大型的工程开发里面,这种相似的代码会经常出现,那么如果有办法把这些相似代码提取并复用,对工程的可维护性和开发效率可以带来明显的提升。
    使用HOC我们可以提供一个方法,并接受不了组件和一些组件间的区别配置作为参数,然后返回一个包装过的组件作为结果。

    function withSubscription(WrappedComponent, selectData) {
      // ...and returns another component...
      return class extends React.Component {
        constructor(props) {
          super(props);
          this.handleChange = this.handleChange.bind(this);
          this.state = {
            data: selectData(DataSource, props)
          };
        }
    
        componentDidMount() {
          // ... that takes care of the subscription...
          DataSource.addChangeListener(this.handleChange);
        }
    
        componentWillUnmount() {
          DataSource.removeChangeListener(this.handleChange);
        }
    
        handleChange() {
          this.setState({
            data: selectData(DataSource, this.props)
          });
        }
    
        render() {
          // ... and renders the wrapped component with the fresh data!
          // Notice that we pass through any additional props
          return <WrappedComponent data={this.state.data} {...this.props} />;
        }
      };
    }
    

    然后我们就可以通过简单的调用该方法来包装组件:

    const CommentListWithSubscription = withSubscription(
      CommentList,
      (DataSource) => DataSource.getComments()
    );
    
    const BlogPostWithSubscription = withSubscription(
      BlogPost,
      (DataSource, props) => DataSource.getBlogPost(props.id)
    );
    

    注意:在HOC中我们并没有修改输入的组件,也没有通过继承来扩展组件。HOC是通过组合的方式来达到扩展组件的目的,一个HOC应该是一个没有副作用的方法。

    相关文章

      网友评论

          本文标题:高阶组件(HOC)

          本文链接:https://www.haomeiwen.com/subject/jxksvctx.html