Talking about React high-end components

安歌
中文

Preamble

I have been busy with my trivia in May and June. In July (July 31 is still July), I took the time to sort out the old content. Some previous readers mentioned that they wanted to understand the knowledge points of the next high-end components. Sorted it out.

The higher order component HOC (HigherOrderComponent) sounds like an React , but in fact it does not belong to the React API, but should be classified as a use technique or design pattern. First go straight to the essence:

The higher-order component is a function and a function whose parameters are components and the return value is a new component.
More bluntly as follows:

Fn(组件) => 有更强功能地新组件

Fn here is a high-order component.

Component is React a base unit, which typically accept some props property, ultimately shown to be specific to the UI, but in some scenarios conventional assembly is not enough to solve the problem.

Under this dividing line, we can temporarily put aside the boring code and talk about a common scene in life-some milk tea.
In the current life, milk tea has become a joyful adjustment in many people's lives (after all, life is so bitter -_-), and there are many varieties and tastes. For example, the pure tea, milk tea, coffee, are also varied, including cheese, milk, dried fruit, taro mash, etc.... 161056c8a631d7 (Well, I’m going to order a cup of milk tea first, and continue to write after drinking)

Okay, I'm back~

So now you can abstract several basic components:

  • Pure tea
  • milk tea
  • Fruit tea

They can be combined with the following additives:

  • With cheese
  • Add pecan chopped

The logic behaviors of different basic tea feeds are similar, so can be designed as high-end components , which can easily generate different types of final milk tea according to needs. Applying the previous function expression is:

Fn(基础奶茶) => 不同风味的奶茶

where Fn is the charging function. Its function is to turn a basic milk tea into an enhanced milk tea by adding ingredients.

text

At this point, I believe that everyone has a general idea of the role of higher-order functions, and then enter the topic (wake up and boring).

Speaking from a common scenario

I believe that the front-end students have written a lot of back-end systems. Naturally, some common functions are unavoidable, such as operation log printing, permission control etc. Taking operation log printing as an example, the following requirements must be met: when entering certain page components, you need Print out the log and send it to the server.

class Page1 extends React.Component {
  componentDidMount() {
    // 用console.log来模拟日志打印 实际上这里一般会传到服务器保存
    console.log('进入page1');
  }
  
  render(){
    return <div>page1</div>
  }
}

class Page2 extends React.Component {
  componentDidMount() {
    console.log('进入page2');
  }
  
  render(){
    return <div>page2</div>
  }
}

Observe that the Page1 Page2 two components have some similar logic: in the componentDidMount stage, the current page name of console.log

Now move this part of the logic into a function:

function withLog (WrappedComponent, pageName) {
   // 这个函数接收一个组件作为参数
   return class extends React.Component {
     componentDidMount() {
       // 用console.log来模拟日志打印 实际上这里一般会传到服务器保存
       console.log(pageName);
     }
     render (){
       // 此处注意要把this.props继续透传下去
       return <WrappedComponent {...this.props} />;
     }
   }
}

At this point, the logic of printing logs and the association of specific components can be decoupled:

class Page1 extends React.Component {
  // 不必保留打印日志的逻辑了
  render(){
    return <div>page1</div>
  }
}

class Page2 extends React.Component {
  // 不必保留打印日志的逻辑了

  render(){
    return <div>page2</div>
  }
}

// 使用时
const Page1WithLog = withLog(Page1);
const Page2WithLog = withLog(Page2);

In this way, a simple high-end component is realized!

What do high-end components do

As can be seen from the above example, the high-level component is the incoming component, which is packaged in a container component (the container component is the anonymous component returned in the withLog function), and finally a new component with enhanced functions is returned. There is a very key point here:

  • Do not modify the original components!
  • Do not modify the original components!
  • Do not modify the original components!

Students who are familiar with React will find that it implements the idea of functional programming everywhere. Similarly, the high-order component must be a pure function (the same input must return the same result) in order to ensure the reusability of the component,

Combination of high-end components

The case of using a high-end component alone was introduced earlier, so what if you want to use multiple high-end components at the same time? Continuing the previous example, let's design a high-level component that permission management function

function withCheckPermission (WrappedComponent){
    return class extends React.Component {
       async componentWillMount() {
         const hasPermission = await getCurrentUserPermission();
         this.setState({hasPermission});
       }
       render (){
         // 此处注意要把this.props继续透传下去
         return (
           {this.state.hasPermission ? 
            <WrappedComponent {...this.props} /> :
           <div>您没有权限查看该页面,请联系管理员!</div>}
         )
       }
   }
}

checkPermission function will check the user permissions to determine whether the user is allowed to access the current page. Next, we hope to add permissions control and log printing functions Page1

// 当然可以这样写

// 1. 首先附加Log功能
const Page1WithLog = withLog(Page1, 'pageName1');
// 2. 在1的基础上附加CheckPermission功能
const Page1WithLogAndPermission = withCheckPermission(Page1WithLog);

实际上可以直接用 compose 来实现, 这样在使用多个高阶组件时可以更简洁:
// tips:  compose(f, g, h) 等同于 (...args) => f(g(h(...args)))
import {compose} from 'redux';
const Page1WithLogAndPermission = compose(
  Page1WithLogAndPermission,
  (Component) => withLog(Component, 'pageName1'),
);

As mentioned earlier, high-level components will not damage the wrapped component itself, so it is very suitable for flexible use of multiple components. In fact, the effect produced is very similar to wrapping different components on the outer layer of the original component.

Use naming to facilitate debugging

Since high-end components will WrapComponent , in order to facilitate debugging, it is necessary to set the displayName attribute for each high-end component. Take the previous withLog as an example:

function withLog (WrappedComponent, pageName) {
   return class extends React.Component {
     static displayName = `withLog(${getDisplayName(WrappedComponent)})`;
     componentDidMount() {
       // 用console.log来模拟日志打印 实际上这里一般会传到服务器保存
       console.log(pageName);
     }
     render (){
       // 此处注意要把this.props继续透传下去
       return <WrappedComponent {...this.props} />;
     }
   }
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

In this way, the debugging process can easily find every layer of components in the final code through the attributes of the debugger.

Precautions

In fact, most of the matters needing attention are related to the implementation essence of high-end components. The article has always emphasized that the essence of high-end components is: wrap the original WrappedComponent component with a new component, and add some behaviors to the new component, so the package is bound to be Will bring some attention.

Pay attention to passing props

The significance of passing props is naturally needless to say. Except for some exclusive props required by the high-end components themselves, other props should continue to be returned to WrappedComponent , as follows:

function withSomeFeature (WrappedComponent){
    return class extends React.Component {
       // 省略功能
       render (){
         // 此处注意 extraProp表示仅仅是当前这个高阶函数要用的props 
         const { extraProp, ...passThroughProps } = this.props;
         
         // 要把剩下和自己无关的props继续透传下去 
         return<WrappedComponent {...passThroughProps } />
       }
   }
}

Don't use higher-order components in render

Specifically, don't use it like this:

class Test extends React.Component {
  render() {
    const EnhancedComponent = enhance(MyComponent);
    return <EnhancedComponent />;
  }
}

In the above code, each time render is const EnhancedComponent = enhance(MyComponent); returns a different new group (because the final component analysis is actually a object , which is a reference type value, so each definition is equivalent to regenerating an object ), the result of this is that every time the state of the component and all its subcomponents is completely lost. So the correct usage is outside the components, use the new components directly after generating the required new components with high-level components:

const EnhancedComponent = enhance(MyComponent);

class Test extends React.Component {
  render() {
    return <EnhancedComponent />;
  }
}

Copy static method

Again, this problem is caused by the package, assuming WrappedComponent has a very easy to use methods, but enhanced to higher order components, if not addressed, the method is lost:

// WrappedComponent原有一些方法
WrappedComponent.staticMethod = function() {/*...*/}

// 使用 HOC
const EnhancedComponent = enhance(WrappedComponent);
// 增强组件没有 staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true

解决方案就是去复制静态方法,常见复制的方法有两种:
明确知道有哪些静态方法要拷贝, 然后用 Enhance.staticMethod = WrappedComponent.staticMethod; 逐个拷贝;
使用 hoist-non-react-statics 自动拷贝:
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  // 核心代码
  hoistNonReactStatic(Enhance, WrappedComponent);
  return Enhance;
}

Handling Refs

For part of refs that WrapComponent , this cannot be directly props like the attributes of 061056c8a63804. This should be React.forwardRef using 061056c8a63806 API (introduced in React 16.3). The special features of ref will be described in detail later in other articles.

Talk about reverse inheritance

At the end of the article, I also talk about reverse inheritance. The reason why I put it at the end is because this method is not React . The official document has this sentence:

Please note that HOC does not modify the passed-in component, nor does it use inheritance to replicate its behavior. In contrast, HOC composes new components by packaging them in container components. HOC is a pure function with no side effects.

However, I have seen many existing articles that have introduced this usage, so I will briefly introduce it by the way. It is only for understanding and is not recommended (I have not encountered such an indispensable scenario in my current practice. To be added later).

Recall that when I mentioned high-end components, I always mentioned that the decorator mode uses new components to wrap the old components to enhance and other keywords. The difference is that the reverse proxy idea is like this, directly on Sample code:

function withHeader (WrappedComponent){
    // 请注意这里是extends WrappedComponent而不是extends React.Component
    return class extends WrappedComponent {
       render (){
         <div>
            <h1 className='demo-header'></h1>
            // 注意此处要调用父类的render函数
            { super.render() }
         </div>
       }
   }
}

Observe the key part of this example: The high-level component actually returns a WrappedComponent component, which is also the origin of the reverse inheritance naming. In this mode, there are mainly two operations:

  • Rendering hijacking, as the example of FIG seen, in fact, in the new assembly can be controlled in returning WrappedComponent of render results and performing various needs, including selective rendering WrappedComponent subtree
  • Operation state , since the new component by this access to WrappedComponent , so the same can this.state modify it.
    If you really want to use this way of implementing high-level components, you must be very cautious. Render hijacking needs to consider conditional rendering (that is, not completely returning to the subtree), and operation state may also destroy the parent component in some cases The original logic.

with caution, use with caution, use with caution!

Summarize

It’s the end of the water and the water. Let’s briefly review the main content of this article:

  • The essence of a high-level component is a function, the input parameters and return value are components, the role is to enhance a specific function of the component
  • High-end components are recommended for flexible combination use
  • Remember some precautions during use
  • Probably understand the principle of reverse inheritance, but use it with caution

I dug a hole for the ref, because there is still a lot of content to write, in line with the principle of each article should be clear and concise, so that readers can learn knowledge within 10 minutes, and decide whether to write separately later

In the end, first of all, I would like to thank every reader and friend who pays attention (especially the reader who reminded me, if I have the opportunity to invite you to drink milk tea), welcome everyone to pay attention to the column, and hope that everyone will not hesitate to praise and collect the favorite articles. , If you have any comments on the style and content of the text, you are welcome to exchange private messages.

阅读 674

前端路漫漫
路漫漫其修远兮 吾将上下左右东西南北中发白而求索

鹭岛RingCentral前端

6.5k 声望
4.8k 粉丝
0 条评论
你知道吗?

鹭岛RingCentral前端

6.5k 声望
4.8k 粉丝
宣传栏