The Higher Order Component (HOC) is an advanced technique used in React for reusing component logic. HOC itself is not part of the React API, but rather a design pattern based on the compositional nature of React.
The name "Higher Order Component" itself exudes an air of sophistication, and in fact, this concept should be derived from higher-order functions in JavaScript. A higher-order function is a function that takes a function as input or returns a function as output. Currying can be thought of as a kind of higher-order function. Similarly, the React documentation also provides a definition of higher-order components, which are functions that accept a component and return a new component.
Specifically, a higher-order component is a function whose parameter is a component and whose return value is a new component. While a component transforms props into UI, a higher-order component transforms a component into another component. HOCs are common in third-party libraries for React, such as the connect function in Redux and the createFragmentContainer function in Relay.
It's important to note that you should not attempt to modify the component prototype in any way within the HOC. Instead, you should use a compositional approach by wrapping the component in a container component to implement functionality. Generally, there are two typical ways to implement a higher-order component:
For example, we can add a id property value from a store to the incoming component using a higher-order component. This allows us to add new props to the component. Of course, we can also manipulate the props in the WrappedComponent component in JSX, but it's important to note that we shouldn't directly modify the passed-in WrappedComponent class. Instead, we can manipulate it in the process of composition.
We can also use a higher-order component to inject the state of the new component into the wrapped component. For example, we can use a higher-order component to convert an uncontrolled component into a controlled component.
Perhaps our goal is to wrap it with other components to achieve layout or style purposes.
Reverse inheritance means that the returned component inherits from the previous component. In reverse inheritance, we can do a lot of operations, such as modifying state, props, or even flipping the Element Tree. The important point of reverse inheritance is that it cannot guarantee that the entire subtree of child components is resolved. This means that if the resolved element tree contains components (function type or Class type), we cannot manipulate the child components of the components.
When we use reverse inheritance to implement higher-order components, we can control rendering through rendering interception. Specifically, we can consciously control the rendering process of WrappedComponent, thereby controlling the result of rendering control. For example, we can decide whether to render the component based on certain parameters.
We can even intercept the original component's lifecycle through overriding.
Since it is actually an inheritance relationship, we can read the component's props and state, and if necessary, even add, modify, or delete props and state. Of course, you need to control the risks brought by the modifications yourself. In some cases, we may need to pass some parameters to the higher-order attribute, and we can do so by passing parameters in a curried form, combined with higher-order components to achieve similar closure-like operations on components.
Both Mixin and HOC can be used to address cross-cutting concerns.
Mixin is a mixed-in pattern. In practical use, the role of Mixin is still very powerful. It allows us to share the same methods among multiple components, but it also continuously adds new methods and properties to the components. The component itself not only perceives it but even needs to deal with it (such as naming conflicts, state maintenance, etc.). When the number of mixed-in modules increases, the entire component becomes difficult to maintain. Mixin may introduce invisible properties, such as using Mixin methods in rendering components, which introduces invisible properties props and state state to the component. Additionally, Mixin may depend on each other, couple with each other, and is not conducive to code maintenance. In addition, methods in different Mixins may conflict with each other. Previously, the React official recommendation was to use Mixin to address cross-cutting concerns, but because using Mixin may cause more trouble, the official now recommends using HOC.
Higher-order component HOC belongs to the functional programming thought. The wrapped component does not perceive the existence of the higher-order component, and the component returned by the higher-order component has enhanced functionality above the original component. Based on this, React officially recommends using higher-order components.
Do not attempt to modify the component prototype in the HOC, or change it in any other way.
This can lead to some undesirable consequences; one is that the input component can no longer be used like it was before being enhanced with an HOC. Even worse, if you use another HOC that also modifies componentDidUpdate, the previous HOC will become ineffective, and this HOC will also be unable to be applied to function components without lifecycles.
Modifying the passed-in component in an HOC is a poor abstraction, as callers must know how they are implemented to avoid conflicts with other HOCs. HOCs should not modify the passed-in component but should use composition by implementing functionality through wrapping the component in a container component.
HOCs add features to the component and should not significantly alter the convention. The component returned by the HOC should maintain a similar interface to the original component. Most HOCs should include a render method similar to the one below.
Not all HOCs are alike; sometimes it only accepts one parameter, which is the wrapped component.
HOCs can often accept multiple parameters, such as in Relay, where HOC also receives a configuration object to specify the component's data dependency.
The most common HOC signature is as follows. connect is a higher-order function that returns a higher-order component.
This form may seem confusing or unnecessary, but it has a useful property. Single-argument HOCs returned by functions have a signature of Component => Component, making it easy to combine functions with output types similar to input types. This property also allows connect and other HOCs to take on the role of decorators. Furthermore, many third-party libraries provide a compose utility function, including lodash, Redux, and Ramda.
React's diff algorithm uses component identifiers to determine whether to update an existing subtree or discard it and mount a new subtree. If the component returned from render is the same === as the one in the previous render, React recursively updates the subtree by distinguishing between the old and the new one. If they are not equal, it completely unmounts the previous subtree. Normally you don't have to worry about this, but for HOCs, this is very important because it means that you shouldn't apply an HOC to a component within the component's render method.
This is not just a performance issue, remounting a component will cause the component and all of its children to lose their state. If the HOC is created outside of the component, then the component is only created once. So every time it's rendered, it's the same component. Normally, this is what you'd expect. In a few cases when you need to, you can dynamically call an HOC within the lifecycle methods of the component or in its constructor.
Sometimes defining static methods on a React component can be useful, for example, a Relay container exposes a static method getFragment to ease composing GraphQL fragments. But when you apply an HOC to a component, the original component is wrapped by the container component, which means the new component doesn't have any of the original component's static methods.
To solve this issue, you can copy these methods onto the container component before returning it.
To do this, you need to know which methods should be copied, and you can use the hoist-non-react-statics dependency to automatically copy all non-React static methods.
Another viable solution, besides exporting the component, is to separately export the static methods.
Although the convention with higher-order components is to pass all props to the wrapped component, this does not apply to refs because ref is not actually a prop, it's handled specifically by React. If a ref is added to the returned component of an HOC, the ref will refer to the container component instead of the wrapped component. This issue can be addressed by explicitly forwarding the refs to the inner component using the React.forwardRef API.