I have been using Hooks in real projects for almost a year since React 16.8 released hooks. Although there are some drawbacks, hooks are a good feature in general, especially in terms of reducing template code and increasing code reuse. In order to let more people know and use Hook, I decided to write a series of articles related to Hook. This article is the first of this series, mainly talking about why React needs Hook.

The problem solved by Hook

Component non-UI logic is difficult to reuse

For React or other component-based frameworks, pages are made up of UI components. Independent components can be reused in the same project or even in different projects, which greatly improves the efficiency of front-end development. However, aside from UI reuse, some stateful or side effect – dependent non-UI logic was difficult to reuse from component to component. For React, you can duplicate this logic using high-order Components or renderProps, but neither approach is very good and has various problems. If you haven’t used this non-UI logic before, we can start with an example of a higher-order component.

Let’s say you’re developing a personal detail page for a social App where you need to retrieve and display the current user’s online status. You write a component called UserDetail:

class UserDetail extends React.Component {
  state = {
    isOnline: false
  }

  handleUserStatusUpdate = (isOnline) = > {
    this.setState({ isOnline })
  }

  componentDidMount() {
    // The subscriber is online when the component is mounted
    userService.subscribeUserStatus(this.props.userId, this.handleUserStatusUpdate)
  }

  componentDidUpdate(prevProps) {
    // The user information has changed
    if(prevProps.userId ! =this.props.userId) {
      // Unsubscribe from the previous user's status subscription
      userService.unSubscribeUserStatus(this.props.userId, this.handleUserStatusUpdate)
      // Subscribe to the status of the next user
      userService.subscribeUserStatus(this.props.userId, this.handleUserStatusUpdate)
    }
  }

  componentWillUnmount() {
    // Unsubscribe the status subscription when the component is uninstalled
    userService.unSubscribeUserStatus(this.props.userId, this.handleUserStatusUpdate)
  }

  render() {
    return (
      <UserStatus isOnline={this.state.isOnline}>)}}Copy the code

As you can see from the code above, maintaining user status information in the UserDetail component is not a simple task. We need to subscribe and unsubscribe to the user’s status when the component is mounted and unsubscribed, and update the subscription when the user ID changes. Therefore, if another component also needs user presence information, a good programmer like you will not want to simply copy and paste this part of the logic, because repetitive code logic is very bad for code maintenance and refactoring. Let’s see how we can reuse this logic using higher-order component methods:

// withUserStatus.jsx
const withUserStatus = (DecoratedComponent) = > {
  class WrapperComponent extends React.Component {
   state = {
      isOnline: false
    }

    handleUserStatusUpdate = (isOnline) = > {
      this.setState({ isOnline })
    }

    componentDidMount() {
      // The subscriber is online when the component is mounted
      userService.subscribeUserStatus(this.props.userId, this.handleUserStatusUpdate)
    }

    componentDidUpdate(prevProps) {
      // The user information has changed
      if(prevProps.userId ! =this.props.userId) {
        // Unsubscribe from the previous user's status subscription
        userService.unSubscribeUserStatus(this.props.userId, this.handleUserStatusUpdate)
        // Subscribe to the status of the next user
        userService.subscribeUserStatus(this.props.userId, this.handleUserStatusUpdate)
      }
    }

    componentWillUnmount() {
      // Unsubscribe the status subscription when the component is uninstalled
      userService.unSubscribeUserStatus(this.props.userId, this.handleUserStatusUpdate)
    }

    render() {
      return <DecoratedComponent
        isOnline={this.stateIsOnline}
        {. this.props} / >
    }
  }

  return WrapperComponent
}
Copy the code

In the code above we define a higher-level component to get the user’s presence, which maintains the current user’s presence information and passes it as a parameter to the decorated component. We can then use this higher-order component to refactor the UserDetail component’s code:

import withUserStatus from 'somewhere'

class UserDetail {
  render() {
    return <UserStatus isOnline={this.props.isOnline}>
  }
}

export default withUserStatus(UserDetail)
Copy the code

You can see that with the withUserStatus higher-order component, the UserDetail component is much less code, and now it just needs to get the isOnline parameter from the parent component to display. And this higher-order component can be applied to any other component that needs to retrieve user presence information, so you don’t need to maintain the same code on the front end anymore.

It is important to note that the logic of the high-level component encapsulation above has little to do with UI presentation. It maintains the side effect of obtaining and updating the user’s presence information, which interacts with the outside world, and the storage of the user’s state, which is related to the component’s state. While the code may seem elegant, the use of higher-order components to encapsulate the logic of a component has the following problems:

  • The development of higher-level components is not developer-friendly: it takes a while for developers (especially junior developers) to understand how it works and get used to writing it. If you’ve been using higher-order components for a long time, you might look at this statement with a grain of salt. However, I’m sure it takes you a while to figure out how advanced components work when you first start working with them, and they’re pretty clunky from the examples above. Imagine that one day you have a React newbie on your project. It will probably take him a while to understand the higher-level component code you write.
  • Poor composability between higher-order componentsFor those of you who use higher-order components, you may have tried nesting multiple higher-order components for the same component because you need to add different functions to the component, such as this code:withAuth(withRouter(withUserStatus(UserDetail))). This nesting of higher-order components can cause a number of problems, one of which is the problem of props missing. For example, a prop passed from withAuth to UserDetail may be lost or overwritten in the withUserStatus component. This is fine if you are using advanced components that are written by yourself, as debugging and modification is easier than if you are using third-party libraries.
  • Wrapper hell is easy to happenReact Devtools: This problem occurs when multiple higher-order components are nested, which makes it very difficult to view and debug a component in React Devtools. Here’s a picture to get a feel for it:

    This is really advanced components of the moment, the problem on the crematorium feeling.

Similar to higher-order components, renderProps have the same problem. For these reasons, React needed a new way to reuse non-UI logic between components, so hooks were born. In general, Hooks have the following advantages over higher-order components and renderProps in terms of reusing code logic:

  • Simple to write: Each Hook is a function, so it is simple to write and easier for developers to understand.
  • Simple composition: Hooks are very simple to compose, and components only need to use multiple hooks at the same time to use all of their functions.
  • Easy to expand: Hooks are highly extensible. You can customize hooks to extend the functionality of a particular Hook.
  • No Wrapper hell: A Hook does not change the hierarchy of the component and there is no wrapper hell problem.

In addition to replacing hard-to-use HOC and renderProps for non-UI logic reuse of components, hooks also address the following issues.

Component lifecycle functions are not suitable for managing Side Effect logic

In the UserDetail component above, we scattered the side effect related logic to obtain the user’s online state into three life cycle functions componentDidMount, componentWillUnmount and componentDidUpdate. This interlinked logic being scattered into different functions can lead to bugs and data inconsistencies. In addition to this, we may place a lot of separate Side effect logic in the same lifecycle function of the component. For example, if we want to change the title of the browser’s current TAB to the current user name when the user views a user’s details page, Document.title = this.props. UserName = document.title = this.props. UserName = document.title = this.props. The pieces of code that are not connected but are put together just get bigger and bigger, and your components become harder to test. This shows that Class Component lifecycle functions are not suitable for managing Component Side Effect logic.

Then how does Hook solve this problem? Since every Hook is a function, you can useEffect Hook all side effect logic in the same function. This approach has many advantages. First, all the associated codes are put together, which is very convenient for code maintenance. Second, a certain side effect Hook can be reused by different components to improve development efficiency. For example, we can wrap the logic to change the title of the TAB page in a custom Hook that can be used if other components have the same logic:

// Custom Hook
function useTabTitle(title) {
  React.useEffect((a)= > {
    document.title = title
  }, [title])
}

// Use useTabTitle Hook in UserDetail
function UserDetail = (props) = >{
  useTabTitle(props.userName)
  ...
}
Copy the code

This ability to reuse Side Effect is actually a very powerful feature. If you check your current project code, there must be many components that can be packaged as hooks. The side effect encapsulated as Hook can not only be used in a certain project, but also be reused in different projects, which will definitely improve our development efficiency greatly.

Unfriendly Class Component

Besides the fact that Class Component lifecycle functions are unsuitable for Side Effect management, there are other problems.

First, Class Component is not developer-friendly. If you want to use Class Component, you need to understand how this is used in JS. It’s used quite differently from other languages. For JS reasons, you need to manually bind this to the registered Event Listener in the Class Component. Otherwise, this is undefined. Early React players must have known that manually binding this to every event listener was tedious and bug-prone, a problem that didn’t change until class Properties came along.

class UserDetail extends React.Component {
  constructor(props) {
    super(props)
    this.handlerUserStatusUpdate = this.handleUserStatusUpdate.bind(this)... }}Copy the code

In addition to being unfriendly to developers, Class Component is also unfriendly to machines. For example, Class Component lifecycle functions are hard to minified. Second, the existence of a Class Component may hinder the evolution of React. For example, with the rise of the new concept of Compiler as Framework, Frameworks such as Svelte, Angular, and Glimmer incorporate the concept of a framework at compile time to remove runtime code from Production Code to speed up the first screen load of an application. This approach is already being adopted and is likely to become a trend in the future. If you are not familiar with the Compiler as Framework concept, check out my other post: The Complete Beginner’s Guide to Svelte 3. React has existed for 5 years, and it should keep up with the trend if it wants to continue to exist for more than 5 years. For this reason, the React team and Prepack team made some attempts related to Compiler as Framework, and this idea has a lot of imagination according to the current experimental results. However, the React developers also discovered a serious problem in the process. Developers might use Class Component in unconventional ways, which would reduce the optimization effect of the solution.

React will have to get developers to use Function components more than Class components if it wants to take off. One of the main reasons developers prefer Class Component over Function Component is that Function Component lacks state management and lifecycle functions. This problem goes away because developers can use useState hooks to useState and useEffect hooks in Function components to implement functions similar to lifecycle functions. Most importantly, React encapsulates all complex implementations in a framework that allows developers to develop with Hooks without learning the concepts of functional and reactive programming.

conclusion

React has a Hook. There are three reasons why React has a Hook.

  • Component non-UI logic is difficult to reuse.
  • Component lifecycle functions are not suitable for managing Side Effect logic.
  • Unfriendly Class Component.

If you have something else to add or if you think I’m wrong, feel free to discuss it with me in the comments section, and IN a future article I’ll give you an in-depth look at some of the commonly used hooks.

reference

  • React Today and Tomorrow and 90% Cleaner React With Hooks
  • React Hook RFC

Personal Technology dynamics

The article started on my personal blog

Welcome to pay attention to the public number of green Onions to learn and grow together