React Higher Order Components (HOC)

 Original address: https://github.com/easyui/blog/blob/master/ReactNative/2017-11-03-React-Native-%E9%AB%98%E9%98%B6%E7%BB%84% E4%BB%B6.md

 

 

React Higher Order Components (HOC)

background

The birth of this way of writing high-level components comes from the practice of the community, the purpose is to solve some cross-cutting concerns. The earliest official solution of React was to use mixin.

Definition of Higher Order Functions

When it comes to higher-order components, we have to briefly introduce higher-order functions. The following shows a simplest higher-order function

const add = (x,y,f) => f(x)+f(y)

When we call add(-5, 6, Math.abs), the parameters x, y and f receive -5, 6 and Math.abs respectively. According to the function definition, we can deduce the calculation process as:

x ==> -5
y ==> 6
f ==> abs
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11

Higher Order Components (HOC)

definition

So, what are higher-order components? Analogous to the definition of higher-order functions, a higher-order component is a function that accepts a component as a parameter and returns a new component. It should be noted here that the higher-order component is a function, not a component, which must be noted.

At the same time, it is emphasized here that higher-order components are not React APIs themselves. It's just a pattern, a pattern that inevitably arises from the compositional nature of React itself. More generally speaking, the higher-order component wraps the incoming React component, goes through a series of processing, and finally returns a relatively enhanced React component for other components to call. A higher order component is just a React component that wraps another React component.

Essentially a class factory with pseudocode for function labels below it inspired by Haskell

hocFactory:: W: React.Component => E: React.Component
//这里 W(WrappedComponent) 指被包装的 React.Component,E(Enhanced Component) 指返回的新的高阶 React 组件。

The term "packaging" in the definition is deliberately vague because it can refer to two things:

  • Props Proxy: Higher-order components manipulate the props passed to WrappedComponent,
  • Inheritance Inversion: Higher-order components extend WrappedComponent.

accomplish

In this section we'll learn about two mainstream approaches to implementing higher-order components in React: Props Proxy and Inheritance Inversion. Two methods encapsulate several ways of wrapping WrappedComponent.

####Props Proxy (PP)

The implementation of the property proxy is as follows:

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    render() {
      return <WrappedComponent {...this.props}/>
    }
  }
}

As you can see, the render method of the higher-order component here returns a React Element of type WrappedComponent (that is, the wrapped component), and we pass the props received by the higher-order component to it, hence the name Props Proxy.

####Reverse inheritance (II) can be implemented simply like this:

function iiHOC(WrappedComponent) {
  return class Enhancer extends WrappedComponent {
    render() {
      return super.render()
    }
  }
}

As you can see, the returned higher-order component class (Enhancer) extends WrappedComponent. This is called reverse inheritance because WrappedComponent is passively inherited by Enhancer, rather than WrappedComponent inheriting Enhancer. In this way their relationship is reversed.

Reverse inheritance allows higher-order components to get WrappedComponent through the this keyword, which means it can get state, props, component lifecycle hooks, and render methods.

Note: You cannot change or create props to WrappedComponent instances because React doesn't allow changing the props received by a component, but you can change the props of child elements/child components in the render method.

use

In the process of React development, it is found that there are many situations in which components need to be "enhanced", such as adding or modifying some specific props to the component, some permission management, or some other optimizations. And if this function is for multiple components, and each component writes the same set of code at the same time, it is obviously not very wise, so you can consider using HOC.

effect

Code reuse, code modularization, logical abstraction, and bootstrap code extraction

State abstraction and change

You can abstract state by passing props and callbacks (callback functions) to WrappedComponent, which is very similar to Presentational and Container Components, another component composition idea in React.

Example: In the following abstract state example, we naively (naively :D) abstracted the value and onChange of the name input. I say it's naive because it's not common to write this way, but you get the point.

function ppHOC(WrappedComponent) {
  return class PP extends React.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}/>
    }
  }
}

Then use it like this:

@ppHOC
class Example extends React.Component {
  render() {
    return <input name="name" {...this.props.name}/>
  }
}

The input here automatically becomes a controlled input .

Add, delete and read props

Be careful when modifying or removing important props, you should probably assign namespaces to higher-order component props to prevent breaking props passed to WrappedComponent from outside.

For example, if you want to add a props to wrappedComponent, you can do it like this:

function control(wrappedComponent) {
  return class Control extends React.Component {
    render(){
      let props = {
        ...this.props,
        message: "You are under control"
      };
      return <wrappedComponent {...props} />
    }
  }
}

This way, you can use the message props in your components:

class MyComponent extends React.Component {
  render(){
    return <div>{this.props.message}</div>
  }
}

export default control(MyComponent);

render hijacking

The render hijacking here is not that you can control the details of its rendering, but to control whether to render. Since the details are controlled by the render method inside the component, you cannot control the rendering details.

For example, if the component wants to load... when the data is not loaded, it can be written like this:

function loading(wrappedComponent) {
  return class Loading extends React.Component {
    render(){
      if(this.props.data) {
        return <div>loading...</div>
      }
      return <wrappedComponent {...props} />
    }
  }
}

In this way, when the parent does not pass in data, only loading... will be displayed in this section, and the specific content of the component will not be displayed.

class MyComponent extends React.Component {
  render(){
    return <div>{this.props.data}</div>
  }
}

export default control(MyComponent);

Notice

####Try not to arbitrarily modify the props required by subordinate components The reason why I say this is because modifying the props passed from the parent to the subordinates has certain risks, which may cause errors in the subordinate components. For example, if the props of a name were originally required, but were deleted in the HOC, the subordinate components may not be rendered normally, or even an error will be reported.

####Component上面绑定的Static方法会丢失 比如,你原来在Component上面绑定了一些static方法MyComponent.staticMethod = o=>o。但是由于经过HOC的包裹,父级组件拿到的已经不是原来的组件了,所以当然无法获取到staticMethod方法了。

官网上的示例:

// 定义一个static方法
WrappedComponent.staticMethod = function() {/*...*/}
// 利用HOC包裹
const EnhancedComponent = enhance(WrappedComponent);

// 返回的方法无法获取到staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true
这里有一个解决方法,就是hoist-non-react-statics组件,这个组件会自动把所有绑定在对象上的非React方法都绑定到新的对象上:

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

Ref无法获取你想要的ref

以前你在父组件中使用的时候,你可以直接通过this.refs.component进行获取。但是因为这里的component经过HOC的封装,Props Proxy 作为一层代理,会发生隔离,因此传入 WrappedComponent 的 ref 将无法访问到其本身,需在 Props Proxy 内完成中转,具体可参考以下代码,react-redux 也是这样实现的。

此外各个 Props Proxy 的默认名称是相同的,需要根据 WrappedComponent 来进行不同命名。

function ppHOC(WrappedComponent) {
  return class PP extends React.Component {
    // 实现 HOC 不同的命名
    static displayName = `HOC(${WrappedComponent.displayName})`;

    getWrappedInstance() {
      return this.wrappedInstance;
    }

    // 实现 ref 的访问
    setWrappedInstance(ref) {
      this.wrappedInstance = ref;
    }

    render() {
      return <WrappedComponent {
        ...this.props,
        ref: this.setWrappedInstance.bind(this),
      } />
    }
  }
}

@ppHOC
class Example extends React.Component {
  static displayName = 'Example';
  handleClick() { ... }
  ...
}

class App extends React.Component {
  handleClick() {
    this.refs.example.getWrappedInstance().handleClick();
  }
  render() {
    return (
      <div>
        <button onClick={this.handleClick.bind(this)}>按钮</button>
        <Example ref="example" />
      </div>  
    );
  }
}

参考:

React 高阶组件浅析 React高阶组件(HOC)模型理论与实践 深入理解 React 高阶组件 精读 React 高阶组件 React Native创建高阶组件(修饰器) rn参考代码: withOrientation.js 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326366334&siteId=291194637