Preface β οΈ
- As a member of the front-end team, I have never been exposed to React advanced components. When I first saw the name π±, I was confused and even had the idea of “starting to give up”.
- However, π¦’, after further study, is actually a very simple concept, but very common things. Its function is to achieve code reuse and logical abstraction, on
state
εprops
Abstraction and manipulation, refinement of components (such as adding life cycles), rendering hijacking, etc. - Because of the availability of higher-order components πͺ, it is frequently used in large quantities
React.js
Related third-party libraries such asReact-Redux
(Used to manage react application state, portal πRedux + React-router introduction π and configuration π©πΎπ» tutorial),React-Loadable
(used to load higher-order components with dynamically imported components), etc. - Introduced so much, the following into the topic, through the introduction of the relevant basic knowledge and practice scenarios, take you to the high-level components π.
Basic concepts of higher-order components (what are they β)
- HOC (higher-order Components) is not a component, but a function that takes a component as an argument and returns a modified new component:
const EnhancedComponent = higherOrderComponent(WrappedComponent);
Copy the code
- It is important to distinguish between components that will
props
A higher-order component is a component that is converted to another component. - High-order components are an advanced technique used in React to reuse component logic. See the official documentation for details on how this works.
Reasons for using higher-order components (why β)
- In business development, although it is possible to complete project development without mastering higher-order components, if we can flexibly use higher-order components (plus π₯°), the project code can become more elegant, while enhancing the reusability and flexibility of the code, and improving the development efficiency.
- At the same time, understanding higher-order components helps us understand various kinds of
React.js
The principle of third-party libraries is very helpful π. - The problems that higher-order components can solve can be summarized in the following three aspects:
- Extract repetitive code to realize component reuse. Common scenario: page reuse.
- Conditional rendering, controlling the rendering logic of components (render hijacking), common scenarios: permission control.
- Capture/hijack the life cycle of the component being processed, common scenarios: component rendering performance tracking, logging.
- It can be seen that the role of higher-order components is very powerful πͺ. Next, I will introduce the implementation of higher-order components, so as to deepen our understanding of the role of higher-order components.
Implementation of higher-order components (how to do β)
- Typically, higher-order components can be implemented in one of two ways:
- Properties Proxy (Props Proxy)
- Returns a stateless function component
- Returns a class component
- Inheritance Inversion
- Properties Proxy (Props Proxy)
- The differences in how higher-order components are implemented determine their respective application scenarios: one
React
The component containsprops
,state
,ref
, life cycle method,static
Methods andReact
Element tree has several important parts, so I will compare the differences between the two higher-order component implementations in the following aspects:- Whether the original component can be wrapped
- Whether the original component is inherited
- Can read/manipulate the original component
props
- Can read/manipulate the original component
state
- Whether through
ref
Access to the original componentdom
The element - Whether it affects some of the life cycles of the original component
- Whether to fetch the original component
static
methods - Can hijack the original component lifecycle method
- Can render hijack
The property broker
- Property brokering is the most common implementation, and it is essentially a composite approach that implements functionality by wrapping components in container components.
- The life cycle relationship between high-order components implemented by property proxy and the original component is exactly the life cycle relationship between the React parent component. Therefore, high-order components implemented by property proxy will affect some life cycle methods of the original component.
Operating props
- The simplest property broker implementation code is as follows:
// Return a stateless function componentfunction HOC(WrappedComponent) {
const newProps = { type: 'HOC' };
returnprops => <WrappedComponent {... props} {... newProps}/>; } // Return a stateful class componentfunction HOC(WrappedComponent) {
return class extends React.Component {
render() {
const newProps = { type: 'HOC' };
return<WrappedComponent {... this.props} {... newProps}/>; }}; }Copy the code
- As you can see from the code above, a higher-order component wrapped by a property broker can intercept a component passed by its parent
props
In advance toprops
Do something like add onetype
Properties.
Abstract the state
- Note β οΈ that higher-order components implemented through property brokers cannot operate directly on the original component
state
But it can passprops
And the callback functionstate
Abstraction. οΈ - A common example is the transition from an uncontrolled component to a controlled component:
// Higher-order componentsfunction HOC(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
name: ' '}; this.onChange = this.onChange.bind(this); } onChange = (event) => { this.setState({ name: event.target.value, }) }render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onChange,
},
};
return<WrappedComponent {... this.props} {... newProps} />; }}; } @hoc class Example extends Component {render() {
return <input name="name" {...this.props.name} />;
}
}
Copy the code
Get the REFS reference
- In order to access
DOM element
οΌfocus
Events, animations, using third-party DOM manipulation libraries), sometimes we use componentsref
Attributes, aboutrefs
Please refer toThe official documentation. ref
Properties can only be declared on components of class type, not on components of function type (because stateless components have no instances).- A higher-order component implemented by property proxy cannot directly obtain the original component
refs
References, however, can be passed in the original component’sref
In the callback function called by the parent componentref
Callback function to get the original component’srefs
References. - Let’s say I have a
User
Component (original component), which has the following code:
import * as React from 'react';
import * as styles from './index.module.less'; interface IProps { name: string; age: number; inputRef? : any; } class User extends React.Component<IProps> { private inputElement: any ; staticsayHello () {
console.error('hello world'); // tslint:disable-line
}
constructor (props: IProps) {
super(props);
this.focus = this.focus.bind(this);
this.onChange = this.onChange.bind(this);
}
state = {
name: ' ',
age: 0,
};
componentDidMount () {
this.setState({
name: this.props.name,
age: this.props.age,
});
}
onChange = (e: any) => {
this.setState({
age: e.target.value,
});
}
focus () {
this.inputElement.focus();
}
render () {
return(<div className={styles.wrapper}> <div className={styles.namewrapper}> Name: {this.state.name}</div> <div className={styles.ageWrapper}> Age: <input className={styles.input} value={this.state.age} onChange={this.onChange}type="number"
ref={input => {
if(this.props.inputRef) { this.props.inputRef(input); } this.inputelement = input;} this.inputelement = input; } /> </div> <div> <button className={styles.button} onClick={this.focus} > Get the focus of the input box </button> </div> </div>); }}export default User;
Copy the code
- A property broker can retrieve the original component
refs
The high-level component code referenced is as follows:
import * as React from 'react';
import * as styles from './index.module.less';
function HOC (WrappedComponent: any) {
let inputElement: any = null;
function handleClick () {
inputElement.focus();
}
function wrappedComponentStaic () {
WrappedComponent.sayHello();
}
return(props: any) => ( <div className={styles.hocWrapper}> <WrappedComponent inputRef={(el: any) => { inputElement = el; }} {... props} /> <inputtype="button"
value="Get child component input box focus"
onClick={handleClick}
className={styles.focusButton}
/>
<input
type="button"
value="Call the child static"
onClick={wrappedComponentStaic}
className={styles.callButton}
/>
</div>
);
}
export default HOC;
Copy the code
- Use:
import React from 'react';
import HOC from '.. /.. /components/OperateRefsHOC';
import User from '.. /.. /components/User';
const EnhanceUser = HOC(User);
class OperateRefs extends React.Component<any> {
render () {
return <EnhanceUser name="Xiao Ming"age={12} />; }}export default OperateRefs;
Copy the code
- After being wrapped by higher-order components
EnhanceUser
The component can be accessedUser
In the componentinput
Chemical element:
Gets the static method of the original component
- When the component to be processed is a class component, the higher-order component implemented through the property broker (whether it returns a function component or a class component) can obtain the static method of the original component, as shown in the code of the higher-order component above. The core code is as follows:
import * as React from 'react';
import * as styles from './index.module.less';
functionHOC (WrappedComponent: any) {/* omit irrelevant code... * /function wrappedComponentStaic () {
WrappedComponent.sayHello();
}
return(props: any) => ( <div className={styles.hocWrapper}> <WrappedComponent inputRef={(el: any) => { inputElement = el; }} {... Props} /> /*... */ <inputtype="button"
value="Call the child static"
onClick={wrappedComponentStaic}
className={styles.callButton}
/>
</div>
);
}
export default HOC;
Copy the code
- The effect is as follows:
Conditional rendering is implemented using props
- Higher-order components implemented through property brokers cannot directly implement rendering hijacking of the original component (i.e., inside the original component)
render
The control is not very strong) but can be passedprops
To control whether to render and pass in data:
import * as React from 'react';
import * as styles from './index.module.less';
functionHOC (WrappedComponent: any) {/* omit irrelevant code... * /function wrappedComponentStaic () {
WrappedComponent.sayHello();
}
return(props: any) => ( <div className={styles.hocWrapper}> { props.isShow ? ( <WrappedComponent {... Props} />) : <div> No data </div>} </div>); }export default HOC;
Copy the code
Wrap the incoming component with other elements
- We can achieve layout or style purposes by wrapping the original components in ways like the following:
function withBackgroundColor(WrappedComponent) {
return class extends React.Component {
render() {
return (
<div style={{ backgroundColor: '#ccc'}}> <WrappedComponent {... this.props} {... newProps} /> </div> ); }}; }Copy the code
Reverse inheritance
- Reverse inheritance refers to using a function that takes a component passed in as an argument and returns a component of the class that inherits the component
render()
Method returnssuper.render()
Method, the simplest implementation is as follows:
const HOC = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
returnsuper.render(); }}}Copy the code
- In contrast to property proxy, high-order components implemented using reverse inheritance allow high-order components to pass
this
Access to the original component, so you can directly read and manipulate the original component’sstate
/ref
/ Lifecycle approach. - Higher-order components implemented by reverse inheritance can pass
super.render()
Method to get the object of the component instance passed inrender
Result, so you can render hijack the incoming component (the biggest feature), as in:- Conditionally present the element tree (
element tree
) - Operation by
render()
The output of theReact
Element tree - In any
render()
The output of theReact
Operation in elementprops
- Wrap the rendering result of the incoming component with other elements
- Conditionally present the element tree (
Hijack the original component lifecycle method
- Because a high-order component implemented in reverse inheritance returns a new component that inherits from the passed component, when the new component defines the same method, instance methods of the parent (passed component) are overridden, as shown in the following code:
functionHOC(WrappedComponent){// Inherits the incoming componentreturnClass HOC extends WrappedComponent {// Note: This overrides the componentDidMount methodcomponentDidMount(){
...
}
render(){// Use super to call the render method of the passed componentreturnsuper.render(); }}}Copy the code
- Although lifecycle overrides are covered, we can hijack the lifecycle in other ways:
functionHOC(WrappedComponent){ const didMount = WrappedComponent.prototype.componentDidMount; // Inherits the incoming componentreturn class HOC extends WrappedComponent {
componentDidMount(){// hijack the lifecycle of the WrappedComponentif(didMount) { didMount.apply(this); }... }render(){// Use super to call the render method of the passed componentreturnsuper.render(); }}}Copy the code
Read/manipulate the state of the original component
- High-order components implemented in reverse inheritance can read, edit, and delete incoming component instances
state
, as shown in the following code:
functionHOC(WrappedComponent){ const didMount = WrappedComponent.prototype.componentDidMount; // Inherits the incoming componentreturn class HOC extends WrappedComponent {
async componentDidMount() {if(didMount) { await didMount.apply(this); This.setstate ({number: 2}); }render(){// Use super to call the render method of the passed componentreturnsuper.render(); }}}Copy the code
Rendering hijacked
Conditions apply colours to a drawing
- Conditional rendering means that we can decide whether or not to render a component based on some parameters (similar to property brokering), such as:
const HOC = (WrappedComponent) =>
class extends WrappedComponent {
render() {
if (this.props.isRender) {
return super.render();
} else {
return<div> Currently no data </div>; }}}Copy the code
Modify the React element tree
- We can still get through
React.cloneElement
Method modified byrender
The React component tree output from the React method:
// Example from Inside the React Stackfunction HigherOrderComponent(WrappedComponent) {
return class extends WrappedComponent {
render() {
const tree = super.render();
const newProps = {};
if (tree && tree.type === 'input') {
newProps.value = 'something here'; } const props = { ... tree.props, ... newProps, }; const newTree = React.cloneElement(tree, props, tree.props.children);returnnewTree; }}; }Copy the code
Property proxy versus reverse inheritance
-
The previous two sections describe higher-order components implemented by property broking and reverse inheritance, respectively:
- Property brokering is done from a “composite” perspective, which facilitates manipulation from the outside
WrappedComponent
, the objects that can be operated on areprops
Or, inWrappedComponent
Add some interceptors, controllers, etc. - Reverse inheritance operates from the inside from the perspective of inheritance
WrappedComponent
, which can manipulate the inside of a componentstate
, life cycle,render
Functions and so on.
- Property brokering is done from a “composite” perspective, which facilitates manipulation from the outside
-
For the sake of comparison, the list of features for the higher-order components implemented in the two ways is as follows:
Feature list The property broker Reverse inheritance Whether the original component can be wrapped Square root Square root Whether the original component is inherited x Square root Can read/manipulate the original component props
Square root Square root Can read/manipulate the original component state
δΉ Square root Whether through ref
Access to the original componentdom
The elementδΉ Square root Whether it affects some of the life cycles of the original component Square root Square root Whether to fetch the original component static
methodsSquare root Square root Can hijack the original component lifecycle method x Square root Can render hijack δΉ Square root -
As you can see, the high-order components implemented by reverse inheritance are more powerful and personalized than those implemented by property brokers, so they can adapt to more scenarios.
Specific practice π»
- This section describes some practices of high-level components in business scenarios π°.
Page reuse
- As mentioned earlier, the property broker is the most common high-level component implementation, which is essentially a composite approach to reuse component logic by wrapping components in container components. Therefore, if you want to reuse pages, you can use higher-order components implemented in the property broker way.
- Let’s say we have in our project
pageA
εpageB
The two UIs interact with exactly the same movie list page, but because they belong to different movie categories, the data source and some of the copywriting are different, it might be written like this:
// views/PageA.js
import React from 'react';
import fetchMovieListByType from '.. /lib/utils';
import MovieList from '.. /components/MovieList';
class PageA extends React.Component {
state = {
movieList: [],
}
/* ... */
async componentDidMount() {
const movieList = await fetchMovieListByType('comedy');
this.setState({
movieList,
});
}
render() {
return <MovieList data={this.state.movieList} emptyTips="No comedy yet."/ >}}export default PageA;
// views/PageB.js
import React from 'react';
import fetchMovieListByType from '.. /lib/utils';
import MovieList from '.. /components/MovieList';
class PageB extends React.Component {
state = {
movieList: [],
}
// ...
async componentDidMount() {
const movieList = await fetchMovieListByType('action');
this.setState({
movieList,
});
}
render() {
return <MovieList data={this.state.movieList} emptyTips="No action movies yet."/ >}}export default PageB;
Copy the code
- By observing that the code on both pages has a lot of the same code, you may at first think you can muddle through π€¦βοΈ. However, with the development of the business, more and more types of movies need to be online. Every time a new page is written, some repeated code will be added, which is obviously unreasonable π , so we need to extract the repeated logic in the page π¬ :
// HOC
import React from 'react';
const withFetchingHOC = (WrappedComponent, fetchingMethod, defaultProps) => {
return class extends React.Component {
async componentDidMount() {
const data = await fetchingMethod();
this.setState({
data,
});
}
render() {
return( <WrappedComponent data={this.state.data} {... defaultProps} {... this.props} /> ); }} // use: // views/ pagea.js import React from'react';
import withFetchingHOC from '.. /hoc/withFetchingHOC';
import fetchMovieListByType from '.. /lib/utils';
import MovieList from '.. /components/MovieList';
const defaultProps = {emptyTips: 'No Comedy yet'}
export default withFetchingHOC(MovieList, fetchMovieListByType('comedy'), defaultProps);
// views/PageB.js
import React from 'react';
import withFetchingHOC from '.. /hoc/withFetchingHOC';
import fetchMovieListByType from '.. /lib/utils';
import MovieList from '.. /components/MovieList';
const defaultProps = {emptyTips: 'No action movie yet'}
export default withFetchingHOC(MovieList, fetchMovieListByType('action'), defaultProps);;
// views/PageOthers.js
import React from 'react';
import withFetchingHOC from '.. /hoc/withFetchingHOC';
import fetchMovieListByType from '.. /lib/utils';
import MovieList from '.. /components/MovieList'; const defaultProps = {... }export default withFetchingHOC(MovieList, fetchMovieListByType('some-other-type'), defaultProps);
Copy the code
- You can see the higher-order components designed above
withFetchingHOC
, the changed parts (components and methods of getting data) are pulled out and passed in, thus achieving page reuse.
Access control
- Imagine a scenario where a new feature has recently come online, containing a series of newly developed pages. You need to add the whitelist function to some of these pages. If users who are not in the whitelist visit these pages, they will only be prompted by copywriting and do not display relevant service data. After one week (function acceptance is completed), the whitelist will be removed and all users will be available.
- There are several conditions in the above scenario:
- Multiple page authentication: The authentication code cannot be written repeatedly in the page component.
- Not whitelisted users only copy prompt: authentication process before business data request;
- Remove whitelist after a period of time: Authentication should be decoupled from services. Adding or removing authentication should minimize impact on the original logic.
- Encapsulate the authentication process and use the conditional rendering feature of higher-order components. If authentication fails, relevant documents are displayed. If authentication succeeds, business components are rendered. Since both attribute brokering and reverse inheritance can implement conditional rendering, we will use the higher-order components of the simpler attribute brokering approach to solve the problem:
import React from 'react';
import { whiteListAuth } from '.. /lib/utils'; /** * Whitelisted permissions * @param WrappedComponent * @returns {AuthWrappedComponent} * @constructor */function AuthWrapper(WrappedComponent) {
return class AuthWrappedComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
permissionDenied: -1,
};
}
async componentDidMount() { try { await whiteListAuth(); This.setstate ({permissionDenied: 0,}); } catch (err) { this.setState({ permissionDenied: 1, }); }}render() {
if (this.state.permissionDenied === -1) {
returnnull; // Authentication interface request not completed}if (this.state.permissionDenied) {
return<div> is coming soon, please look for ~</div>; }return<WrappedComponent {... this.props} />; }}}export default AuthWrapper;
Copy the code
- For pages that require permission control, just pass the page component as a parameter to the higher-order component
AuthWrapper
β . - Use of higher-order components to completely decouple authentication from transactions and avoid redundant business requests when authentication fails. Add/remove a small amount of code to add/remove controls of user whitelist. Logic of existing business components does not affect ββοΈβοΈοΈ.
Component rendering performance tracking
- The previous two examples used property brokers to implement higher-order components. This section introduces π°, which uses reverse inheritance to implement higher-order components to track component rendering performance.
- As mentioned earlier βοΈ, can high-order components implemented by reverse inheritance hijack the original component lifecycle method? Therefore, we can easily record the rendering time of a component by using this feature:
import React from 'react'; Class Home extends React.Component {render () {
return (<h1>Hello World.</h1>);
}
}
// HOC
function withTiming (WrappedComponent: any) {
let start: number, end: number;
return class extends WrappedComponent {
constructor (props: any) {
super(props);
start = 0;
end = 0;
}
componentWillMount () {
if (super.componentWillMount) {
super.componentWillMount();
}
start = +Date.now();
}
componentDidMount () {
if (super.componentDidMount) {
super.componentDidMount();
}
end = +Date.now();
console.error(`${WrappedComponent.name}Component rendering time is${end - start} ms`);
}
render () {
returnsuper.render(); }}; }export default withTiming(Home);
Copy the code
- Results:
Extended Reading (Q & A)
Will hooks replace higher-order components?
Hook
ζ―16.8 the React
The new feature that allows us to write without writingclass
In case of usestate
And so forthReact
Characteristics (aboutHook
The related introduction can be readThe official documentation).Hook
The advent of the Internet has made a lot of awkward writing easier, most typically it can be replacedclass
Most of the functionality in the lifecycle is put together with more related logic rather than being scattered across lifecycle instance methods.- although
Hook
It solves a lot of problems, but that obviously doesn’t meanHook
Can replace higher-order components, because they still have their own advantages:- Higher-order components can easily inject functionality into a base by external protocols
Component
, so it can be used to make plug-ins, such asreact-swipeable-views
In theautoPlay
Higher-order components, stateful by injectionprops
Instead of writing code directly to the main library. forHook
The intermediate process must be strongly dependent on the target componentHook
It’s justHook
Clearly not designed to solve plug-in injection problems). Hook
It can be seen more as a complement to higher-order component solutions, filling in the parts that higher-order components are not good at.Hook
“Makes code more compact and easier to doController
Or the logic that requires cohesion.- At present
Hook
It’s still early days (The React 16.8.0
Before it was officially released.Hook
Stable version), some third party libraries may not be compatible for the time beingHook
.
- Higher-order components can easily inject functionality into a base by external protocols
React
The authorities have not yetclass
δ»React
To remove,class
The component andHook
It can exist at the same time. Officials also recommend avoiding any “extensive refactoring,” after allHook
Is a very new feature that can be used in new non-critical code if you likeHook
.
Conclusion π
- A higher-order component is not a component; it is a pure function that converts one component into another.
- The main function of higher-order components is to achieve code reuse and logical abstraction
state
εprops
Abstraction and manipulation, refinement of components (such as adding life cycles), rendering hijacking, etc. Rational use of higher-order components in actual business scenarios can improve development efficiency and code maintainability. - The availability of higher-order components πͺ makes them frequently available in large quantities
React.js
Related third-party libraries such asReact-Redux
theconnect
Methods,React-Loadable
And so on are used to understand the higher-order components for our understanding of variousReact.js
The principle of third-party libraries is very helpful π. - Higher-order components can be implemented in two ways, namely property proxy and reverse inheritance. It can be seen as a decorator pattern in
React
Implementation: Implements enhancements to component functionality without modifying the original component. - For details, see π : React + typescript project customization process.
If there are any omissions in the above content, please leave a message βοΈ to point out, and progress together πͺπͺπͺ
If you find this article helpful, ππ leave your precious π
The resources
- Advanced Components (official documentation)
- The React high-order component takes three questions to heart
- React Goes from Mixin to HOC to Hook
- React Hooks on HoC and Render Props
- Do you really have React Hooks right?
- Getting started with ECMAScript 6 — Decorators