As we all know, React has two forms of components, class components and function components, and developers can use class components and function components to achieve the same purpose and build the exact same page. Since I came across React last August, I have been using function components because they are recommended by my company. I have to say that function components are much better than class components. You can use useEffect to implement
componentWillUnmount of the class component. While marveling at the power of hooks, I’ve often wondered how React actually implements the useEffect and useState hooks? Each time a function component is rendered, the component function is simply executed once, the function is not instance (no this pointer), so how is the state of the component recorded and updated? Maybe you are like me, you will guess that the state of the function component is achieved by closures, then congratulations, your guess is right, hook is the use of the idea of closure. For those who don’t know about closures, please move to MDN to learn about the concept of closures before coming here to continue reading.
This article is about 15 minutes long, so I’m sure you’ll get something out of it. The article focuses on deepening your understanding of how hooks work by manually implementing a mock React yourself. It includes the most commonly used useEffect and useState implementations. I refer to some blogs and materials, and then manually organize their own practice.
I. The Basics
Before mocking the implementation of React, we must understand some concepts.
2. Rendering is actually executing a function
Each time a function component re-renders, it actually executes the component’s function once.
For example, the App component below is actually executing App(props) each time it re-renders.
II. Simulating React
1. A simple React
First we implement the simplest React ourselves, with only the render function. render takes a Component as an argument and exampleProps represents the Props passed to the component.
In order to test this simple version of React, we need a component whose render function outputs some information.
Then apply it in practice to see the effect.
We Component rendered twice, and both renders printed log messages.
Next, we extend React to implement useState. useState can return a value and a dispatcher that can update it. useState accepts an initial value parameter that sets the initial value of the state.
Returns a stateful value, and a function to update it.
The function component uses useState to create and update the state of the component, which is one of the most common hooks in react. Let’s implement useState ourselves.
The function of closures is used here. We store the state values into React.state array and then create a currentSetter which is used to update the state values. Finally index is added to store the new state value. Due to the nature of closures, when there are multiple useState values within a component function, each cachedIndex is independent, so the state corresponding to each currentSetter is also independent. Another thing to note is that
React.index = 0 must be reset in the render function, because the index must be 0 every time the component function is executed, because the hook is also executed every time.
Update the Component and add state to it.
Test component rendering.
The useState we simulated functions as expected. It is quite different from the actual useState in React, but it is enough to help us understand it.
useEffect is executed asynchronously, after the component function is executed. Here’s how the documentation describes useEffect, and I’ve excerpted a few of the most important features.
- The function passed to
useEffectwill run after the render is committed to the screen.
- By default, effects run after every completed render, but you can choose to fire them only when certain values have changed.
- the function passed to
useEffectmay return a clean-up function.
The first point means that useEffect is asynchronous, the second point means that we can pass a second parameter (dependency) to useEffect to control whether the first function parameter in useEffect is executed or not, and the third point is that the function we pass can return a function as a cleanup or unsubscribe function.
We will follow the above three features to implement our own useEffect.
Store the dependencies in React.state and compare the dependencies with React.state[cachedIndex] (the previously stored dependencies) to determine if the dependency has changed. If it has changed, we execute a callback and update the dependencies stored in React.state for the next execution.
We also update the Component to include the useEffect hook.
The final return object contains an unsubscribe, which is used to simulate the cleanup work of useEffect.
Test the useEffect function.
As you can see, the useEffect function is executed when the component is first loaded, and also when the name is changed, and finally the unsubscribe is called to perform the cleanup.
This is almost the end of the content, you must have a deeper understanding of hooks. There are more similar hooks that you can try to simulate yourselves, such as useCallback.