Beginners might be a little scared when they see higher-level components, because I was the same way. Now that React is released in 16.13, there are a lot of new operations and rationalization. Is there any reason not to take a look at high-end operations?

React Component Classification

Let’s take a look at the react component classes. One is a stateful component (class component), the other is a stateless component (function component).

Stateful components are familiar to most of us, and are often used to death. Now we’ll focus on stateless components, which is fundamental to understanding higher-order components.

Stateless component: Essentially a pure function that constructs components in the form of functions

Pure function: The output is completely determined by the input, without any side effects

Render is a pure function, and the output is related only to state and props

Currently, stateless function components are preferred, which only have props but no state

// Const Person => <div>{props. Name}</div> class Person extends React.Component{render(){return()  <div>{props.name}</div> ) } }Copy the code

Higher-order functions vs higher-order components

Higher-order functions: Receive a function and return a function

const identity = func => func 
Copy the code

Higher-order component: Receives a component and returns a component

const identity = BaseComponent => BaseComponent
Copy the code

Because components can be functional, higher-order components are essentially higher-order functions.

React describes a component that takes a React component as input and outputs a new React component (enhanced component).

Main function: Encapsulate and extract the common logic of components, so that this part of logic can be reused between components.

High order component writing

React components are classified as stateless and stateful. There are two ways to write high-order components.

const HOC = (BaseComponent) = > class extends React.Component{
    render(){
        return(
            <BaseComponent
                {. this.props} / >)}}Copy the code
const HOC = BaseComponent= > props => <BaseComponent {. props} / >
Copy the code

Note: You want the props to be inherited into the returned component

Hands-on advanced components

Let’s take a look at some small but beautiful higher-order components to get a general idea of how higher-order components are written and what they do.

withLoading

For example, we already have a List component, now we need to add a loading judgment, we used to add the render method, but this will change the structure of the List component. This logic needs to be rewritten if loading is added to other components. To do this, we can write a higher-order component to pull the logic out of this loading.

// withLoading.js
const withLoading = BaseComponent= >({isLoading, ... otherProps}) => ( isLoading ?<div>Loading in...</div> : 
    <BaseComponent {. otherProps} / >
)
export default withLoading;
Copy the code

In this way, a higher-order component withLoading can pass isLoading to a normal component.

A List component is wrapped with a withLoading component, withLoading(List), so that the List becomes an enhanced component and can be loaded with isLoading

// List.js
function List (props) {
  return (
      <ul>
        {
          props.data.map((item, idx) => {
            return <li key={idx}>{item}</p></li>
          })
        }
      </ul>
  )
}
export default withLoading(List)
Copy the code
// app.js

<List data={list} isLoading={false} ></List> // Return to loading...

<List data={list} isLoading={true} ></List> // Return the List component
Copy the code

withLocalStorage

In components, we often need to interact withLocalStorage. We can make a higher-order component withLocalStorage to extract the interaction logic.

const withLocalStorage = BaseComponent= > props => {
  let getLocalStorage = key= > localStorage.getItem(key)
  let setLocalStorage = (key, value) = > localStorage.setItem(key, value)
  let removeLocalStorage = key= > localStorage.removeItem(key)
  return <BaseComponent 
          getLocalStorage={getLocalStorage}
          setLocalStorage={setLocalStorage}
          removeLocalStorage={removeLocalStorage}
          {. props} / >
}
export default withLocalStorage;

Copy the code

flattenProp

You now have a User component

const User = props= > {
  return (
    <div>
      <div>Name: {props. User. The username}</div>
      <div>Age: {props. User. Age}</div>
    </div>)}export default User;

/ / call
<User user={user}></User> //const user = {username: 'zhangsan', age: 20}
Copy the code

Now, to flatten the user attribute, you can do a flattening prop component.

const flattenProp = propKey= > BaseComponent => props= ><BaseComponent {... props} {... props[propKey]} />export default flattenProp;
Copy the code

On a User component, a flattening of User attributes by a higher-order component prop, the flattener props (‘ User ‘)(User)

const User = props= > {
  return (
    <div>
      <div>Name: {props. The username}</div>
      <div>Age: {props. Age}</div>
    </div>)}export default flattenProp('user')(User); ! [](https://user-gold-cdn.xitu.io/2020/6/22/172da1f3dc2eef45? w=478&h=276&f=jpeg&s=134318)
Copy the code

renameProp

Also, we can rename props.

const renameProp = (oldProp, newProp) = > BaseComponent => props= > {
  lethoc = { ... omit(props, [oldProp]), [newProp]: props[oldProp] }return <BaseComponent {. hoc} / >
}

export default renameProp;
Copy the code

RenameProp (‘username’, ‘name’)(User) renameProp(‘username’, ‘name’)(User)

const User = props => {
  return(< div > < div > name: {props. The name} < / div > < div > age: {props. Age} < / div > < / div >)}export default flattenProp('user')(renameProp('username'.'name')(User))
Copy the code

Notice above that two higher-order components have been grouped together. Later we can compose multiple higher-order components using recompose’s compose method.

withProps

Add the props

const withProps = ownerProps= > BaseComponent => props= > {
  if (typeof ownerProps == 'function') {
    ownerProps = ownerProps(props)
  }
  return <BaseComponent {. props} {. ownerProps} / >
}

export default withProps
Copy the code

Arguments to withProps can be objects or functions. In the case of a function, the argument is the parent component props, which returns an object.

withProps({math: 90.english: 40})

withProps(({math, english, chinese}) = > ({
    total: math + english + chinese
}))
Copy the code

mapProps

Filter props, and finally preserve only the returned props

const mapProps = func= > BaseComponent => props= > {
  return <BaseComponent {. func(props)} / >
}
export default mapProps
Copy the code

The argument to mapProps is a function. The function takes props of the parent component and returns an object.

mapProps(({math, english, chinese}) = > ({
  total: math + english + chinese
}))
Copy the code

In addition to the above manipulation, we can also extract the state. Usually with the Class component setState, there is a whole page rendering overhead. Extract state and manage it internally within a widget without the unnecessary overhead of calling setState externally.

withState

Add state and update state. The basic idea is to make props of state and the functions that update state.

const withState = (stateName, stateUpdaterName, initialState) = > BaseComponent => {
  const factory = React.createFactory(BaseComponent)
  class withState extends React.Component {
    state = {
      stateValue: typeof initialState === 'function' ? initialState(this.props) : initialState
    }

    updateStateValue = (updateFn, callback) = > 
      this.setState(({stateValue}) = > ({
        stateValue: typeof updateFn === 'function' ? updateFn(stateValue) : updateFn
      }), callback)
    
    render () {
      returnfactory({ ... this.props, [stateName]:this.state.stateValue,
        [stateUpdaterName]: this.updateStateValue
      })
    }
  }
  return withState
}

export default withState;
Copy the code

The first argument withState is the name of the state, the second argument is the name of the function that changed the state, and the third argument is the initial value.

withState('count'.'changeCount'.0)
Copy the code

withHandlers

Handle event interactions.

const withHandlers = handlers= > BaseComponent => {
  const factory = React.createFactory(BaseComponent)
  var mapValues = (obj, func) = > {
    let result = {}
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        result[key] = func(obj[key], key)
      }
    }
    return result
  }

  class withHandlers extends React.Component {
    handlers = mapValues(
      typeof handlers === 'function' ? handlers(this.props) : handlers,
      handlerFunc => (. args) = > {
        var handler = handlerFunc(this.props)
        if (typeofhandler ! = ='function') {
          return handler
        }
        returnhandler(... args) } ) render () {returnfactory({ ... this.props, ... this.handlers }) } }return withHandlers
}
Copy the code
withHandlers({
  handleChangeCount: props= > e => {
    props.changeCount(2)}})// It can also be written like this
withHandlers({
  handleChangeCount: props= > props.changeCount(2)})Copy the code

You usually use withState and withHandlers together

compose(
  withState('count'.'changeCount'.0)
  withHandlers({
    handleChangeCount: props= > props.changeCount(2)}))Copy the code

Use the recompose

These higher-order components are already packaged for Recompose. We can invoke these components directly using the Recompose package.

The installation

yarn add recompose
Copy the code

use

import {compose, flattenProp, renameProp, withProps, withState, withHandlers} from 'recompose'

compose(
  flattenProp('user'),
  renameProp('username'.'name'),
  withProps({
    sex: 'male'
  }),
  withState('count'.'changeCount'.0)
  withHandlers({
    handleChangeCount: props= > props.changeCount(2)
  })
)(User)
Copy the code

Enter the props

{
    user: {username: 'zhangsan', age: 20}
}
Copy the code

After processing props

{
    name: 'zhangsan',
    age: 20,
    sex: 'male',
    count: 0,
    changeCount,
    handleChangeCount
}
Copy the code

The last

Recompose also has a number of other advanced components for those interested in exploring.

Documents: recompose

Making: recompose