High Order Component(包装组件,前面简称HOC),是React开发中进步组件复用性的高级技巧。HOC并不是React的API,他是依据React的个性造成的一种开发模式。
HOC具体上就是一个承受组件作为参数并返回一个新的组件的办法
const EnhancedComponent = higherOrderComponent(WrappedComponent)
在React的第三方生态中,有十分多的应用,比方Redux的connect
办法或者React-Router的withrouter
办法。
举个例子
咱们有两个组件:
// CommentListclass 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> ); }}
// BlogPostclass 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} />; }}
参考 前端react面试题具体解答
他们尽管是两个不同的组件,对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是个纯正的办法,所以当前如果有类似的组件,都能够通过该办法进行包装,可能节俭十分多的反复代码。
不要批改原始组件,应用组合进行性能扩大
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 receivedconst 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 receivedconst 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,拿到的是包装之后的组件而不是原始组件,这可能就会导致一些问题。