preface

In the use of React, developers always carried out modular and component work in a “lazy” spirit. From mixins at the very beginning, to HOC and render props, all of them were fighting for this purpose. However, they also had their drawbacks. HOC has multiple layers of nesting, and props are covered. How to transition from HOC to Hooks?

What is HOC?

Official documentation and personal understanding

The React documentation defines HOC like this:

HOC is an advanced technique for reusable component logic in React. HOC itself is not part of the React API; it is a design pattern based on the composite features of React.

The higher-order component itself is a function and can provide the same functions as functions, that is, input and output. It outputs a new component with the common data and methods we need by processing the input component and other parameters.

A simple requirement

Now that online courses are popular, take the homework platform of online courses as an example. Imagine a requirement that homework assigned by the teacher should be displayed in a list. If the current user is a teacher, the function of adding homework will be added, and the code like this will be written:

class HomeWorkList extends React.Component {
  constructor(props){
    super(props)
    this.state = {
      list: []
    }
  }
  
  getList = (a)= > {
    http.get('/homework/list')
    	.then(result= > {
      	if (result && result.success) {
         this.setState({
           list: result.data,
         })
         return;
        }
      	console.log('error:', result)
    	})
    	.catch(ex= > {
      	console.log('ex:', ex)
    	})
  }
  /** * @param {number} type Indicates the update type. 1 is new, and 2 is modified. * @param {object} data Indicates the data to be updated
  updateList = (type, data) = > {
    if (type === 1) {
	    this.setState({ list: this.state.list.concat(data) })
    } else if (type === 2) {
      this.setState({ 
        list: this.state.list.map(item= > 
          item.id === data.id ? data : item)
      	})
    }
  }
  
  componentDidMount() {
    this.getList();
  }
  render() {
    const { isTeacher } = this.props;
    return( <LayoutContainer> { this.state.list.map(item => ( <HomeWorkItem data={item} isTeacher={isTeacher} update={data => This.updatelist (2, data)} /> {/* This component provides a modification method, after the modification is successful call update */}))} {isTeacher? (<AddHomeWork update={data => this.updatelist (1, data)} /> {/* If (AddHomeWork update={data => this.updatelist (1, data)}) : null } </LayoutContainer> ) } }Copy the code

There’s nothing special about the code above, and it’s not worth picking on, so is that it?

Another simple requirement

However, when you realize that the requirement for the list of student submissions is also to list all student submissions, provide the teacher with a grade for each assignment, and provide an entry point for students who do not submit homework, you will write the same code as above:

class SubmitList extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      list: []
    }
  }
  getList = (a)= > {
    http.get('/result/list/')
    	.then(result= > {
      	// Omit some judgment
      	this.setState({ list: result.data })
    	})
  }
  updateList = (type, data) = > {
    / / same as above
  }
  
  render() {
    const { isTeacher, isStudent, submited } = this.props;
    return( <LayoutContainer> { this.state.list.map(item => ( <ResultItem data={item} update={data => this.updateList(2, data)} /> )) } { isStudent && ! submited ? <SubmitHomework update={data => this.updateList(1, data)} /> : null } </LayoutContainer> ) } }Copy the code

As you can see, there is a lot of identical code between the two components, and if there is a similar list requirement, there is a lot of similar duplicate code to be written. In this case, consider splitting the common part into HOC.

Advanced component modification

const withList = ({ url }) = > RenderComponent => {
	return class extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        list: []
      }
    }
    getList = (a)= > {
      http.get(url)
      	.then(result= > {
        	if (result.success) {
	        	this.setState({ list: result.data })
          } else {
            console.log('error:', result)
          }
      	})
      	.catch(ex= > {
        	console.log('ex:', ex)
      	})
    }
    updateList = (type, data) = > {
      if (type === 1) {
        this.setState({ list: this.state.list.concat(data) })
      } else if (type === 2) {
        this.setState({ 
          list: this.state.list.map(item= > 
            item.id === data.id ? data : item)
          })
      }
    }
    componentDidMount() {
      this.getList()
    }
    render() {
      const { list } = this.state
      return <RenderComponent list={list} update={this.update} />}}}Copy the code

At this point, it is easy to implement the above two functions and similar functions. I usually use decorators (annotations) :

// List of jobs
@withList({ url: '/homework/list' })
class HomeWorkList extends React.Component {
  render() {
  	const { list, isTeacher, update } = this.props;
    return( <LayoutContainer> { list.map(item => ( <HomeWorkItem data={item} isTeacher={isTeacher} update={data => update(2, Data)} /> {/* This component provides the modification method, after the modification is successful call update */})} {isTeacher? <AddHomeWork update={data => update(1, data)} /> {AddHomeWork update={data => update(1, data)} Null} </LayoutContainer>)}} @withList({url: '/homework/result' }) class ResultList extends React.Component { render() { const { isTeacher, isStudent, submited, update, list } = this.props; return ( <LayoutContainer> { list.map(item => ( <ResultItem data={item} update={data => update(2, data)} /> )) } { isStudent && ! submited ? <SubmitHomework update={data => update(1, data)} /> : null } </LayoutContainer> ) } }Copy the code

Instead of writing the same bunch of redundant code each time for similar requests for additions, deletions, changes, and reviews, you can just use HOC to render the corresponding content entry.

Before the appearance of HOC, mixins were commonly used. A series of problems brought by mixins have long been agreed and will not be described here.

Finally, the update data in 🌰 can also be broken down into higher-order components, which I won’t go into here. Let’s look at React Hooks.

React Hooks

Document interpretation

Hook is a new feature in React 16.8 that allows you to use state and other React features without having to write classes.

There are four commonly used hooks:

useState()
useContext()
useReducer()
useEffect()
Copy the code

As the name suggests, it’s easy to see what these hooks mean.

For example, useState is a state hook, and for a purely functional component (a puppet component, also known as a dumb component), there is no state, so put its state under the hook.

UseContext is a shared-state hook. Context itself is a component top-level API. This hook allows you to subscribe to context without using connect.

React does not provide state management. Redux is usually used to manage state. This hook introduces the Reducer function of Redux.

UserEffect is literally a side effect hook. On the front end, the most common side effect is to request data from the server. This component can replace features such as componentDidMount in the Class component.

Rewrite HOC with React Hooks

In general, the hooks article stops almost there. So, how to replace HOC with a Hook, and briefly, how to rewrite the first example about logging clicks with a Hook, I wrote the following code:

import { useState, useEffect } from 'react'

export default function(url) {
    let [list, setList] = useState([]);

    useEffect((a)= > {
        http.get(url)
            .then(result= > {
                if (result.success) {
                    setList(result.data)
                } else {
                    console.log('error:', result)
                }
            })
            .catch(ex= > {
                console.log('ex:', ex)
            })
    }, []) // Setting the second parameter to null has the same effect as componentDidMount

    const update = (type, data) = > {
        if(type === 1) {
            setList(list.concat(data))
        } else if (type === 2) {
            setList(list.map(item= > item.id === data.id ? data : item))
        }
    }

    return [list, update]
}
Copy the code

It’s easy to use

// List of jobs
const HomwWorkList = ({ isTeacher }) = > {
  const [list, update] = useList('/homework/list')
  return( <LayoutContainer> { list.map(item => ( <HomeWorkItem data={item} isTeacher={isTeacher} update={data => update(2, Data)} /> {/* This component provides the modification method, after the modification is successful call update */})} {isTeacher? <AddHomeWork update={data => update(1, data)} /> {AddHomeWork update={data => update(1, data)} Null} </LayoutContainer>)} // Submit job list const ReulstList = ({isTeacher, isStudent, submited}) => {const [list, update] = useList('/homework/result') return ( <LayoutContainer> { list.map(item => ( <ResultItem data={item} update={data => update(2, data)} /> )) } { isStudent && ! submited ? <SubmitHomework update={data => update(1, data)} /> : null } </LayoutContainer> ) }Copy the code