乐趣区

关于react.js:你是如何使用React高阶组件的

High Order Component(包装组件,前面简称 HOC),是 React 开发中进步组件复用性的高级技巧。HOC 并不是 React 的 API,他是依据 React 的个性造成的一种开发模式。

HOC 具体上就是一个承受组件作为参数并返回一个新的组件的办法

const EnhancedComponent = higherOrderComponent(WrappedComponent)

在 React 的第三方生态中,有十分多的应用,比方 Redux 的 connect 办法或者 React-Router 的 withrouter 办法。

举个例子

咱们有两个组件:

// 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 应该是一个没有副作用的办法。

在这个例子中咱们把两个组件类似的生命周期办法提取进去,并提供 selectData 作为参数让输出组件能够抉择本人想要的数据。因为 withSubscription 是个纯正的办法,所以当前如果有类似的组件,都能够通过该办法进行包装,可能节俭十分多的反复代码。

更多 react 面试题解答参见 前端 react 面试题具体解答

不要批改原始组件,应用组合进行性能扩大

function logProps(InputComponent) {InputComponent.prototype.componentWillReceiveProps = function(nextProps) {console.log('Current props:', this.props);
    console.log('Next props:', nextProps);
  };
  // The fact that we're returning the original input is a hint that it has
  // been mutated.
  return InputComponent;
}

// EnhancedComponent will log whenever props are received
const EnhancedComponent = logProps(InputComponent);

通过以上形式咱们也能够达到扩大组件的成果,然而会存在一些问题

  • 如果 InputComponent 自身也有 componentWillReceiveProps 生命周期办法,那么就会被笼罩
  • functional component 不实用,因为他基本不存在生命周期办法

批改原始组件的形式不足抽象化,使用者必须晓得这个办法是如何实现的来防止下面提到的问题。

如果通过组合的形式来做,咱们就能够防止这些问题

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

// EnhancedComponent will log whenever props are received
const EnhancedComponent = logProps(InputComponent);

常规:无关的 props 传入到原始组件

HOC 组件会在原始组件的根底上减少一些扩大性能应用的 props,那么这些 props 就不应该传入到原始组件(当然有例外,比方 HOC 组件须要应用原始组件指定的 props),一般来说咱们会这样解决 props:

render() {
  // Filter out extra props that are specific to this HOC and shouldn't be
  // passed through
  const {extraProp, ...passThroughProps} = this.props;

  // Inject props into the wrapped component. These are usually state values or
  // instance methods.
  const injectedProp = someStateOrInstanceMethod;

  // Pass props to wrapped component
  return (
    <WrappedComponent
      injectedProp={injectedProp}
      {...passThroughProps}    />
  );
}

extraProp是 HOC 组件中要用的 props,不必的剩下的 props 咱们都认为是原始组件须要应用的 props,如果是两者通用的 props 你能够独自传递。

常规:包装组件的显示名称来不便调试

function withSubscription(WrappedComponent) {class WithSubscription extends React.Component {/* ... */}
  WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
  return WithSubscription;
}

function getDisplayName(WrappedComponent) {return WrappedComponent.displayName || WrappedComponent.name || 'Component';}

简略来说就是通过手动指定 displayName 来让 HOC 组件可能更不便得被 react devtool 察看到

常规:不要在 render 办法外面调用 HOC 办法

render() {
  // A new version of EnhancedComponent is created on every render
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // That causes the entire subtree to unmount/remount each time!
  return <EnhancedComponent />;
}

一来每次调用 enhance 返回的都是一个新的 class,react 的 diffing 算法是依据组件的特色来判断是否须要从新渲染的,如果两次 render 的时候组件之间不是 (===) 齐全相等的,那么会间接从新渲染,而部署依据 props 传入之后再进行 diff,对性能损耗十分大。并且从新渲染会让之前的组件的 state 和 children 全副失落。

二来 React 的组件是通过 props 来扭转其显示的,齐全没有必要每次渲染动静产生一个组件,实践上须要在渲染时自定义的参数,都能够通过当时指定好 props 来实现可配置。

静态方法必须被拷贝

有时候会在组件的 class 下面外挂一下帮忙办法,如果依照下面的办法进行包装,那么包装之后的 class 就没有来这些静态方法,这时候为了放弃组件应用的一致性,个别咱们会把这些静态方法拷贝到包装后的组件上。

function enhance(WrappedComponent) {class Enhance extends React.Component {/*...*/}
  // Must know exactly which method(s) to copy :(
  Enhance.staticMethod = WrappedComponent.staticMethod;
  return Enhance;
}

这个之实用于你已知输出组件存在那些静态方法的状况,如果须要可扩展性更高,那么能够抉择应用第三方插件hoist-non-react-statics

import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {class Enhance extends React.Component {/*...*/}
  hoistNonReactStatic(Enhance, WrappedComponent);
  return Enhance;
}

ref

ref 作为 React 中的非凡属性 – 相似于 key,并不属于 props,也就是说咱们应用传递 props 的形式并不会把 ref 传递进去,那么这时候如果咱们在 HOC 组件上放一个 ref,拿到的是包装之后的组件而不是原始组件,这可能就会导致一些问题。

退出移动版