High-order components can encapsulate common logic, pass method attributes to current components, add lifecycle hooks, etc.
Case:
Some pages in a project need to judge their environment. If they are on the mobile side, they will display the page normally and prompt the user about the mobile side environment of the current page. If they are not on the mobile side, they will display the prompt to open it on the mobile side. But some pages don't need this judgment.
If you write a judgment logic on every page, you can use higher-order components to handle this part of the logic.
First create a higher-order component
// src/container/withEnvironment/index.jsx import React from 'react'; const envs = { weixin: 'WeChat', qq: 'QQ', baiduboxapp: 'Mobile Baidu', weibo: 'micro-blog', other: 'Mobile end' } function withEnvironment(BasicComponent) { const ua = navigator.userAgent; const isMobile = 'ontouchstart' in document; let env = 'other'; if (ua.match(/MicroMessenger/i)) { env = 'weixin'; } if (ua.match(/weibo/i)) { env = 'weibo'; } if (ua.match(/qq/i)) { env = 'qq'; } if (ua.match(/baiduboxapp/i)) { env = 'baiduboxapp' } // Return different intermediate components under different logic if (!isMobile) { return function () { return ( <div> <div>This page can only be viewed on the mobile side. Please scan the two-dimensional code below to open it.</div> <div>Suppose we have a two-dimensional code here.</div> </div> ) } } // Pass the page's environment through props to the underlying components through defined intermediate components const C = props => ( <BasicComponent {...props} env={env} envdesc={envs[env]} /> ) return C; } export default withEnvironment;
Then use it in the underlying components
// src/pages/Demo01/index.jsx import React from 'react'; import withEnvironment from '../../container/withEnvironment'; function Demo01(props) { return ( <div>You are now{props.envdesc}Visit this page in</div> ) } export default withEnvironment(Demo01);
Finally, the effect can be seen by rendering the basic components.
// src/index.js import React from 'react'; import { render } from 'react-dom'; import Demo01 from './pages/Demo01'; const root = document.querySelector('#root'); render(<Demo01 />, root);
In the above example, we put the logic of environment judgment in the high-level component, and then only need to execute the page of environment judgment in the basic component.
export default withEnvironment(Demo01);
In addition, we will encounter a very common requirement in the actual development, that is, when entering a page, we need to judge the login status, the different display of login status and non-login status, the different display of roles after login status can be unified through high-level components to handle this logic, and then the login status, role information and so on can be transferred to the basic components.
// Approximate processing logic import React from 'react'; import $ from 'jquery'; // Suppose you have encapsulated a method called getCookie to get cookie s import { getCookie } from 'cookie'; function withRule(BasicComponent) { return class C extends React.Component { state = { islogin: false, rule: -1, loading: true, error: null } componentDidMount() { // If you can find uid directly in cookie, it means that you have logged in and saved the relevant information. if (getCookie('uid')) { this.setState({ islogin: true, rule: getCookie('rule') || 0, loading: false }) } else { // If you can't find uid, try to log in automatically. First, find out from kookie whether the login account and password have been saved. const userinfo = getCookie('userinfo'); if (userinfo) { // Call login interface $.post('/api/login', { username: userinfo.username, password: userinfo.password }).then(resp => { this.setState({ islogin: true, rule: resp.rule, islogin: false }) }).catch(err => this.setState({ error: err.message })) } else { // When you can't login automatically, you can choose to pop up the login box here or display the style of the unlisted page directly. } } } render() { const { islogin, rule, loading, error } = this.state; if (error) { return ( <div>Logon interface request failed! The error message is:{error}</div> ) } if (loading) { return ( <div>Page loading, Just a moment, please....</div> ) } return ( <BasicComponent {...props} islogin={islogin} rule={rule} /> ) } } } export default withRule;
Compared with the first example, this example is closer to practical application and the logic is more complex. So asynchronous data is involved, so the best way is to process logic in the component DidMount of the middleware. In render, different rendering results are determined according to different states.
We need to use react to create components in two ways that are realistic and reasonable. This is crucial. The above two examples are typical enough to represent most situations.
Higher-order components in react-router
In the process of learning react, we will gradually deal with high-level components. The withRouter in react-router should be the first high-level components to be touched. When we use it, we know that through the components wrapped by withRouter, we can access location, router and other objects in props, which is the way withRouter passes through higher-order components.
import React, { Component } from 'react'; import { withRouter } from 'react-router'; class Home extends Component { componentDidMount() { const { router } = this.props; router.push('/'); } render() { return ( <div className="my-home">...</div> ) } } export default withRouter(Home);
Let's look at the source code for withRouter in react-router v4.
import React from 'react'; import PropTypes from 'prop-types'; import hoistStatics from 'hoist-non-react-statics'; import Route from './Route'; // Pass in the base component as a parameter const withRouter = (Component) => { // Creating Intermediate Components const C = (props) => { const { wrappedComponentRef, ...remainingProps } = props; return ( <Route render={routeComponentProps => ( // Wrapped ComponentRef is used to solve the problem that high-order components can not get ref correctly. <Component {...remainingProps} {...routeComponentProps} ref={wrappedComponentRef}/> )}/> ) } C.displayName = `withRouter(${Component.displayName || Component.name})`; C.WrappedComponent = Component; C.propTypes = { wrappedComponentRef: PropTypes.func } // hoistStatics is similar to Object.assign, which is used to solve the problem that the underlying component loses its static method due to the wrapping of higher-order components. return hoistStatics(C, Component); } export default withRouter;
If you're already familiar with examples of high-level components, the source code for withRouter is actually easy to understand. What it does is simply pass routeComponentProps into the underlying components.
In addition, we also need to pay attention to the source code, which solves two problems caused by high-order components. One is that the components wrapped by high-order components can not get the corresponding values through ref correctly when they are used. Second, the static method of the underlying component will also be lost because of the package of the higher-order component. Fortunately, this source code has provided us with a corresponding solution. So if we need to deal with these two points in use, we can do it in the way here.
But in general, we rarely add static methods and use ref in custom components. If you do encounter the need to use them in development, you must pay attention to these two problems of high-order components and seriously solve them.