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 useReact
'sstate
and other features without using classes. You can also create your own customHooks
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 basicHook
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
fromReact
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
andUmi
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: