jeremygo

jeremygo

我是把下一颗珍珠串在绳子上的人

Hooks Discussion

React 16.8 officially introduced the feature of Hooks. During this period, I have also tried Hooks from various aspects and would like to discuss them based on multiple articles.

Hooks allow you to use React's state and other features without using classes. You can also create your own custom Hooks to share reusable state logic between components.

Origin#

Currently, components and top-down data flow help us organize large UIs into small, independent, and reusable parts. However, many times our logic is stateful, so we cannot further break down complex components or extract behavior into a function or another component. These situations include animations, form controls, connecting to external data sources, and so on, which are very common.

Although we already have many ways to reuse this logic in React applications, such as writing simple functions or using components (in the form of functions or classes), it is not as convenient for non-visual logic. Therefore, we have come up with some complex patterns, such as render props and higher-order components.

Wouldn't it be simpler if React had a universal way to reuse logic?

For code reuse, functions seem to be a perfect mechanism, but functions cannot contain local React state. We cannot simply extract behavior from class components, such as updating state based on the window size or making a value change over time. We usually need to refactor this part of the code or use abstract patterns such as observers. These all undermine the simplicity of React.

Based on these considerations, the React development team introduced Hooks. It allows us to use React features (such as state) through functions, and React also provides some built-in Hooks related to React building blocks: state, lifecycle, context. Since Hooks are just regular JavaScript functions, we can also create custom Hooks to use.

Demo#

Let's take a look at an example: updating React state based on the window size.

import { useState, useEffect } from 'react'

function useWindowWidth () {
    const [width, setWidth] = useState(window.innerWidth)
    
    useEffect(() => {
        const handleResize = () => setWidth(window.innerWidth)
        window.addEventListener('resize', handleResize)
        return () => {
            window.removeEventListener('resize', handleResize)
        }
    })
    
    return width
}

The above code uses the two most commonly used built-in Hooks in React: useState and useEffect:

  • useState: Takes an initial state and returns a stateful value and a function to update it.
  • useEffect: Takes a function with side effects (changing data, subscribing, timers, logging, etc.).

React also has a basic Hook called useContext.

At the same time, we also have a custom Hook based on the built-in Hooks: useWindowWidth.

Let's see what useWindowWidth does: it takes the initial size of the window and returns the state value width and the function setWidth to update width. Inside the useEffect, we define a handleResize function that sets the window size using setWidth, and we listen for the window resize event to update the window size. Finally, we return a function to unsubscribe from the event listener.

This is a Hook that adjusts the layout based on the window size.

Imagine how we would implement this using class components:

class setWidth extends React.Component {
    constructor (props) {
        super(props)
        this.state = {
             width: window.innerWidth
        }
    }
	handleResize = () => this.setState({ width: window.innerWidth })
    componentDidMount () {
		window.addEventListener('resize', handleResize)
    }
    componentWillUnmount () {
		window.removeEventListener('resize', handleResize)
    }
    render () {
		return <SomeUI />
    }
}

In comparison, we can easily see that the class component approach requires us to set state, combine with cumbersome lifecycle methods, and render UI (which can be null). The bigger problem is that if we need to reuse this component and <SomeUI /> cannot be the same, we often resort to using higher-order components, which introduces abstract patterns. Therefore, compared to class components, Hooks allow developers to have a lower learning curve and are more in line with the simplicity of React.

Here is a video that provides a more intuitive comparison between class components and Hooks: https://twitter.com/i/status/1056960391543062528.

At the same time, we can also feel the charm of custom Hooks. With the official release of Hooks, there will be more and more Hooks npm packages to better assist developers.

Here is a website that showcases various implementations of custom Hooks: https://usehooks.com/.

In-depth#

As a React developer, I was amazed when Hooks were introduced. From the beginning, React has been positioned to build UIs better, which raises two questions for me:

  • What does the emergence of Hooks represent?
  • How are Hooks implemented?

In the article Thoughts on React Hooks, there is a sentence: "Stateful components don't render, and rendering components don't have state." The correct concept that the React team wants to promote is "separating state from UI". This is actually in line with the characteristics of React Hooks. Let's take a look at the two rules for using Hooks:

  • Only call Hooks at the top level of a function.
  • Only call Hooks from React functions.

Hooks must be written at the top level of a function, which actually makes it easy to develop the habit of writing stateless UI components. Therefore, it is also easier to practice the concept of "separating state from UI". Personally, I think this is one of the reasons why the React team considers Hooks as the perfect solution to the state sharing problem in the long run.

So why must Hooks be called at the top level of a function? It is to prevent us from wrapping Hooks in conditional statements. The reason why conditional wrapping is not allowed with Hooks is related to the implementation of Hooks. To put it simply, Hooks are not implemented using Proxy or getters, but rather through an array-like implementation. Therefore, the normal calling of Hooks is related to the order of indices. Each useState call changes the index. If Hooks are used within conditional statements, it may affect the index and cause errors in Hooks calls. Therefore, it is recommended to perform conditional checks within Hooks as the correct approach.

For specific reasons, please refer to the official documentation.

The principle of Hooks can be found in the article React hooks: not magic, just arrays.

If you want to delve deeper into the Hooks system, you can read Under the hood of React’s hooks system.

Some people may feel that Hooks have some limitations, but a more accurate understanding is that we should follow the conventions of Hooks. It is also the first time that the React team has introduced the concept of "convention over configuration" into the React framework. With limitations, there is also better convenience.

Both Next.js and Umi have the "convention over configuration" feature for routing, which greatly reduces the complexity of route configuration. Similarly, Hooks are like code-level conventions, greatly reducing code complexity. Therefore, we can consider whether this is an important trend for the future.

Further Thoughts#

The design of Hooks is not limited to React and there are already implementations in Vue, Web Components, and even native JavaScript functions (experimental API design).

You may have the question: Does Vue need Hooks? Vue indeed does not have the same problems as React class components. If we need to reuse logic, Mixins can also solve the problem. However, fundamentally speaking, Vue does need Hooks to solve the problem of state sharing. Compared to Mixins, Hooks can help Vue solve two main problems:

  • Implementing state sharing.
  • Clearly showing the flow of logic.

The Vue team also plans to integrate Hooks in Vue 3.0, but it will deviate from the React API and be designed to fit the Vue philosophy. It is also likely to become a replacement for Mixins, so it is worth exploring.

Experimental implementation in Vue: https://github.com/yyx990803/vue-hooks

References:

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.