How To Reuse React Components
render props, and
Hooks are four ways to reuse components
Now frontend engineering is more and more important. Although Ctrl+C and Ctrl+V can also be used to complete requirements, once they are modified, it becomes a huge task. Therefore, copying of code is reduced, and the packaging and reuse capabilities are increased to achieve maintainability and reversibility. The code used becomes particularly important.
In React, components are the main unit of code reuse. The combination-based component reuse mechanism is quite elegant, but for more fine-grained logic (state logic, behavior logic, etc.), reuse is not so easy. It is difficult to disassemble the state logic as a reusable function or component. In fact, before the appearance of Hooks, there was a lack of a simple and direct way of component behavior extension, which is considered to be mixins, higher-order components (HOC), and render props. The upper-level model explored under the existing (component mechanism) game rules has not solved the problem of logic reuse between components from the root. This is my 38th Medium article.
Of course, React no longer recommends using mixins as a reuse solution for a long time, but it can still provide support for mixins through
create-react-class. Note that mixins are not supported when declaring components in ES6 classes.
Mixins allow multiple React components to share code. They are very similar to mixins in Python or traits in PHP. The emergence of the mixin solution comes from an OOP intuition. In the early days, it only provided
React.createClass() API to define components. (In React v15.5.0, it is officially abandoned and moved to
create-react-class). Naturally, (class) inheritance has become an intuitive attempt, and in
mixin scheme. It has become a good solution.
Mixin is mainly used to solve the reuse problem of life cycle logic and state logic, and allows the component life cycle to be extended from the outside. This is especially important in
Flux and other modes, but many defects have also appeared in continuous practice:
- There is an implicit dependency between the component and the
Mixinoften depends on the specific method of the component, but the dependency is not known when the component is defined).
- There may be conflicts between multiple
mixin(such as defining the same
Mixintends to add more states, which reduces the predictability of the application and leads to a sharp increase in complexity.
- Implicit dependencies lead to opaque dependencies, and maintenance costs and understanding costs are rising rapidly.
- It is difficult to quickly understand the behavior of components, and it is necessary to fully understand all the extension behaviors that rely on
mixinand their mutual influence.
- The method and
statefield of the component itself is afraid to be easily deleted because it is difficult to determine whether
mixindepends on it.
Mixinis also difficult to maintain, because
Mixinlogic will eventually be flattened and merged together, and it is difficult to figure out the input and output of a
There is no doubt that these problems are fatal, so
Mixin static crosscutting (similar to inherited reuse) and moved to
HOC higher-order components (similar to combined reuse).
The example of the ancient version, a common scenario is: A component needs to be updated regularly. It is easy to do it with setInterval(), but it is very important to cancel the timer when it is not needed to save memory. React provides a lifecycle method to inform the component. The time of creation or destruction, the following Mixin, use setInterval() and ensure that the timer is cleaned up when the component is destroyed.
Mixin, HOC high-order components take on the heavy responsibility and become the recommended solution for logical reuse between components. High-order components reveal a high-order atmosphere from their names. In fact, this concept should be derived from high-order functions of
React document. Higher-order components receive components and return new components. function. The specific meaning is: High-order components can be seen as an implementation of
React decoration pattern. High-order components are a function, and the function accepts a component as a parameter and returns a new component. It will return an enhanced
React components. High-order components can make our code more reusable, logical and abstract, can hijack the
render method, and can also control
Mixin is a mixed-in mode. In actual use,
Mixin is still very powerful, allowing us to share the same method in multiple components, but it will also continue to add new methods and attributes to the components. The component itself can not only perceive but also need to do related processing (such as naming conflicts, state maintenance, etc.). Once the mixed modules increase, the entire component becomes difficult to maintain.
Mixin may introduce invisible attributes, such as in the
Mixin method used in the rendering component brings invisible property
states to the component.
Mixin may depend on each other and is coupled with each other, which is not conducive to code maintenance. In addition, the methods in different
Mixin may conflict with each other. Previously
React officially recommended using
Mixin to solve problems related to cross-cutting concerns, but because using
Mixin may cause more trouble, the official recommendation is now to use
HOC. High-order component
HOC belong to the idea of
functional programming. The wrapped components will not be aware of the existence of high-order components, and the components returned by high-order components will have a functional enhancement effect on the original components. Based on this,
React officially recommends the use of high-order components.
HOC does not have so many fatal problems, it also has some minor flaws:
- Scalability restriction:
HOCcannot completely replace
Mixin. In some scenarios,
HOCcannot. For example,
HOCcannot access the
Stateof subcomponents from the outside, and at the same time filter out unnecessary updates through
React.PureComponentis provided to solve this problem.
Refis cut off. The transfer problem of
Refis quite annoying under the layers of packaging. The function
Refcan alleviate part of it (allowing
HOCto learn about node creation and destruction), so the
React.forwardRef APIAPI was introduced later.
HOCis flooded, and
WrapperHellappears (there is no problem that cannot be solved by one layer, if there is, then two layers). Multi-layer abstraction also increases complexity and cost of understanding. This is the most critical defect. In
HOCmode There is no good solution.
Specifically, a high-order component is a function whose parameter is a component and the return value is a new component. A component converts
props into a
UI but a high-order component converts a component into another component.
HOC is very common in
React third-party libraries, such as
Attention should be paid here, do not try to modify the component prototype in the
HOC in any way, but should use the combination method to realize the function by packaging the component in the container component. Under normal circumstances, there are two ways to implement high-order components:
- Property agent
- Reverse inheritance
For example, we can add a stored
id attribute value to the incoming component. We can add a
props to this component through high-order components. Of course, we can also operate on the
props in the
WrappedComponent component in
JSX. Note that it is not to manipulate the incoming
WrappedComponent class, we should not directly modify the incoming component, but can operate on it in the process of combination.
We can also use high-order components to load the state of new components into the packaged components. For example, we can use high-order components to convert uncontrolled components into controlled components.
Or our purpose is to wrap it with other components to achieve the purpose of layout or style.
Reverse inheritance means that the returned component inherits the previous component. In reverse inheritance, we can do a lot of operations, modify
props and even flip the
Element Tree. There is an important point in the reverse inheritance that reverse inheritance cannot ensure that the complete sub-component tree is parsed. That means if the parsed element tree contains components (
function type or
Class type), the sub-components of the component can no longer be manipulated.
When we use reverse inheritance to implement high-order components, we can control rendering through rendering hijacking. Specifically, we can consciously control the rendering process of
WrappedComponent to control the results of rendering control. For example, we can decide whether to render components according to some parameters.
We can even hijack the life cycle of the original component by rewriting.
Since it is actually an inheritance relationship, we can read the
state of the component. If necessary, we can even add, modify, and delete the
state. Of course, the premise is that the risks caused by the modification need to be controlled by yourself. In some cases, we may need to pass in some parameters for the high-order attributes, then we can pass in the parameters in the form of currying, and cooperate with the high-order components to complete the operation similar to the closure of the component.
Don’t change the original components
Don’t try to modify the component prototype in
HOC, or change it in other ways.
Doing so will have some undesirable consequences. One is that the input component can no longer be used as before the
HOC enhancement. What is more serious is that if you use another
HOC that also modifies
componentDidUpdate to enhance it, the previous
HOC will be invalid, and this
HOC cannot be applied to functional components that have no life cycle.
HOC of the incoming component is a bad abstraction, and the caller must know how they are implemented to avoid conflicts with other
HOC should not modify the incoming components, but should use a combination of components to achieve functions by packaging the components in container components.
HOC adds features to components and should not significantly change the convention itself. The components returned by
HOC should maintain similar interfaces with the original components.
HOC should transparently transmit
props that have nothing to do with itself, and most
HOC should include a
render method similar to the following.
HOCs are the same. Sometimes it only accepts one parameter, which is the packaged component.
const NavbarWithRouter = withRouter(Navbar);
HOC can usually receive multiple parameters. For example, in
Relay, HOC additionally receives a configuration object to specify the data dependency of the component.
const CommentWithRelay = Relay.createContainer(Comment, config);
The most common HOC signatures are as follows, connect is a higher-order function that returns higher-order components.
This form may seem confusing or unnecessary, but it has a useful property, like the single-parameter
HOC returned by the
connect function has the signature
Component => Component , and functions with the same output type and input type can be easily combined. The same attributes also allow
connect and other
HOCs to assume the role of decorator. In addition, many third-party libraries provide compose tool functions, including
Don’t use HOC in the render method
diff algorithm uses the component identifier to determine whether it should update the existing subtree or discard it and mount the new subtree. If the component returned from the
render is the same as the component in the previous render
React passes The subtree is distinguished from the new subtree to recursively update the subtree, and if they are not equal, the previous subtree is completely unloaded.
Usually, you don’t need to consider this when using it, but it is very important for
HOC, because it means that you should not apply
HOC to a component in the
render method of the component.
This is not just a performance issue. Re-mounting the component will cause the state of the component and all its subcomponents to be lost. If the
HOC is created outside the component, the component will only be created once. So every time you
render it will be the same component. Generally speaking, this is consistent with your expected performance. In rare cases, you need to call
HOC dynamically, you can call it in the component’s lifecycle method or its constructor.
Be sure to copy static methods
Sometimes it is useful to define static methods on
React components. For example, the
Relay container exposes a static method
getFragment to facilitate the composition of
GraphQL fragments. But when you apply
HOC to a component, the original component will be packaged with a container component, which means that the new component does not have any static methods of the original component.
To solve this problem, you can copy these methods to the container component before returning.
But to do this, you need to know which methods should be copied. You can use
hoist-non-react-statics to automatically copy all non-
React static methods.
In addition to exporting components, another feasible solution is to additionally export this static method.
Refs will not be passed
Although the convention of high-level components is to pass all
props to the packaged component, this does not apply to
ref is not actually a
prop, just like a
key, it is specifically handled by
React. If the
ref is added to the return component of the
ref reference points to the container component, not the packaged component. This problem can be explicitly forwarded to the internal component through the
Render props is also a veteran model that has always existed.
render props refers to a simple technology that uses a
props valued as a function to share code between a kind of
React components. A component with
render props receives a function. This function Return a
React element and call it instead of implementing its own rendering logic.
Render props is a function
props used to tell the component what content needs to be rendered. It is also a way to implement component logic reuse. Simply put, it is being copied. In the component used, pass a
prop property named
render (the property name may not
render, as long as the value is a function). The property is a function. This function accepts an object and returns a subcomponent, which will The object in the function parameter is passed as
props to the newly generated component, and when using the caller component, you only need to decide where to
renderthe component and what logic to
renderand pass in the relevant object.
Render props, technically, both are based on the component combination mechanism.
Render props has the same extensibility as
HOC. It is called
Render props. It does not mean that it can only be used to reuse rendering logic, but that it is here. In this mode, the components are combined through
render(), similar to the establishment of a combination relationship through
HOC mode. The two are very similar, and they will also produce a layer of
Wrapper. In fact,
Render props and
HOC . It can even be converted to each other.
Render props will have some problems:
- The data flow is more intuitive. The descendant components can clearly see the source of the data, but in essence,
Render propsis implemented based on closures. A large number of component reuse will inevitably introduce the
- The context of the component is lost, so there is no
this.props.childerncannot be accessed like
There are endless code reuse solutions, but overall code reuse is still very complicated. A large part of this is because fine-grained code reuse should not be bundled with component reuse.
Render props, etc. are based on component combination The solution is equivalent to first packaging the logic to be reused into components, and then using the component reuse mechanism to achieve logic reuse. Naturally, it is limited to component reuse, so there are problems such as limited scalability,
Wrapper Hell, etc. , Then we need to have a simple and direct way of code reuse. Functions. Separating reusable logic into functions should be the most direct and cost-effective way of code reuse. But for state logic, some abstract patterns are still needed. (Such as
Observable) can be reused, which is exactly the idea of
Hooks, using functions as the smallest code reuse unit, and built-in some modes to simplify the reuse of state logic. Compared with the other solutions mentioned above,
Hooks makes the logic reuse within the component no longer bundled with the component reuse. It is really trying to solve the problem of fine-grained logic reuse (between components) from the lower level. In addition, this statement The modular logic reuse scheme further extends the explicit data flow and combination ideas between components to the components.
Hooks are not perfect either, but for now, its disadvantages are as follows:
- The additional learning cost mainly lies in the comparison between
- There are restrictions on the writing method (cannot appear in conditions, loops), and the writing restrictions increase the cost of reconstruction.
- It destroys the performance optimization effect of
React.memoshallow comparison. In order to get the latest
state, the event function must be recreated every
- In closure scenarios, old
propsvalues may be referenced.
- The internal implementation is not intuitive, relying on a mutable global state, and no longer so
React.memocan’t completely replace
state changeis not available, only for
useStateAPIis not perfect in design.