preface
React Hooks basic usage, the official documentation is very detailed. This is the third article in a series looking at the hooks implementation mechanism.
- Drill into React hooks — useState
- Get into React hooks — useEffect
- Personal Blog Address🍹 🍰 fe – code
useState
In this article, we have analyzed the implementation mechanism of useState and useEffect. If you want to learn more about hooks, you can implement the hooks themselves, or read the source code directly. Those of you who are not familiar with some of the basic features of Hooks can refer to the documentation, or to my first two articles.
UseState is the basis on which the hooks scheme can be implemented, which allows components to have their own state without relying on classes. It’s simple to use:
const [count, setCount] = useState(0);
Copy the code
Two things to keep in mind: useState is a function; 2. Each update actually triggers its re-execution (as mentioned in the previous article, because the entire function component is re-executed).
Here’s how to make it simple:
function useState(initialState) {
const state = initialState;
const setState = newState= > state = newState;
return [state, setState];
}
Copy the code
Now of course it’s incomplete because it doesn’t update the data at all. Even when we setState, we trigger the component update, and then the useState update, we just initialize the initialState again. Since the state of useState is a local variable each time, the update is a re-creation, and the previous value is never available. So, we need to save state.
The simplest thing is a global variable, but React isn’t implemented that way. For hooks, React uses linked lists. If you are interested, check them out for yourself.
const HOOKS = []; // Globally store hook variables
let currentIndex = 0; // Global dependency hook execution order subscript
function useState(initialState) {
HOOKS[currentIndex] = HOOKS[currentIndex] || initialState; // Determine if initialization is required
const memoryCurrentIndex = currentIndex; // currentIndex is globally mutable
const setState = newState= > HOOKS[memoryCurrentIndex] = newState;
return [HOOKS[currentIndex++], setState]; // To call hook multiple times, index needs to be +1 each time
}
Copy the code
We save all the hooks in a global array variable, just look for the corresponding subscript on each update. This is why it is important to ensure that the order in which hooks are executed is consistent throughout updates. Imagine that the first run executes four hooks, increasing the subscript to 3; When updating, the original third hook was not executed because of if judgment and other reasons, so is the value of the fourth hook wrong? .
To extend this, useState initialization can pass functions, as can setState, so you can use a little judgment.
function useState(initialState) {
HOOKS[currentIndex] = HOOKS[currentIndex]
|| (typeof initialState === 'function' ? initialState() : initialState);
const memoryCurrentIndex = currentIndex; // currentIndex is globally mutable
const setState = p= > {
let newState = p;
// setCount(count => count + 1
if (typeof p === 'function') newState = p(HOOKS[memoryCurrentIndex]);
// If the values are the same, the update will not be done
if (newState === HOOKS[memoryCurrentIndex]) return;
HOOKS[memoryCurrentIndex] = newState;
};
return [HOOKS[currentIndex++], setState];
}
Copy the code
Tick
Of course, now that we have simply updated the corresponding state without notifying the Render update function, we can write a simple tool.
const HOOKS = []; // Globally store hook variables
let currentIndex = 0; // Global dependency hook execution order subscript
const Tick = {
render: null.queue: [].push: function(task) {
this.queue.push(task);
},
nextTick: function(update) {
this.push(update);
Promise.resolve(() = > {
if (this.queue.length) { // After a loop, all are removed from the stack to ensure that a single event loop does not repeat the rendering
this.queue.forEach(f= > f()); // Execute all tasks in the queue in sequence
currentIndex = 0; // Reset the count
this.queue = []; // Clear the queue
this.render && this.render(); / / update the dom
}
}).then(f= >f()); }};Copy the code
In React, setState updates are synchronous, but we don’t know it, or at least it looks asynchronous. This is because React implements its own set of transaction management. We use Promise instead, which is similar to nextTick in Vue.
const setState = p= > {
/ /...
Tick.nextTick(() = > {
HOOKS[memoryCurrentIndex] = newState;
});
};
Copy the code
useEffect
UseEffect acts as a lifecycle role in the class, and we use it more to notify updates and clean up side effects. Knowing useState, the general principle is the same. If each item in the dependency array is the same as the last one, you don’t need to update it.
function useEffect(fn, deps) {
const hook = HOOKS[currentIndex];
const _deps = hook && hook._deps;
// Check whether the dependency is passed, not default every update
// Determine whether this dependency is the same as the last dependency
consthasChange = _deps ? ! deps.every((v, i) = > _deps[i] === v) : true;
const memoryCurrentIndex = currentIndex; // currentIndex is globally mutable
if (hasChange) {
const _effect = hook && hook._effect;
setTimeout(() = > {
// Check if any side effects need to be removed
typeof _effect === 'function' && _effect();
// Execute this time
const ef = fn();
/ / update the effectsHOOKS[memoryCurrentIndex] = {... HOOKS[memoryCurrentIndex],_effect: ef}; })}// Update dependencies
HOOKS[currentIndex++] = {_deps: deps, _effect: null};
}
Copy the code
You can see that useEffect stores two values in HOOKS, a dependency and a side effect, in the same order in which it is executed. Because useEffect needs to be executed after dom is mounted, setTimeout is used for simple emulation, which is not the case in React.
useReducer
UseReducer is easy to understand, which can be seen as a layer of packaging for useState, so that we can more intuitively manage the state. Remember how to use it:
const reducer = (state, action) = > {
switch (action.type) {
case 'increment':
return {total: state.total + 1};
case 'decrement':
return {total: state.total - 1};
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, { count: 0});
// state.count ...
Copy the code
In other words, we only need to combine useState and Reducer.
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
const update = (state, action) = > {
const result = reducer(state, action);
setState(result);
}
const dispatch = update.bind(null, state);
return [state, dispatch];
}
Copy the code
useMemo & useCallback
Both of these dependencies are used to optimize React performance for handling costly computations. UseMemo returns a value, and useCallback returns a function.
useMemo
function useMemo(fn, deps) {
const hook = HOOKS[currentIndex];
const _deps = hook && hook._deps;
consthasChange = _deps ? ! deps.every((v, i) = > _deps[i] === v) : true;
const memo = hasChange ? fn() : hook.memo;
HOOKS[currentIndex++] = {_deps: deps, memo};
return memo;
}
Copy the code
useCallback
function useCallback(fn, deps) {
return useMemo(() = > fn, deps);
}
Copy the code
summary
This simple simulation implemented part of React Hook. If you are interested, you can improve it by yourself. If you find any problems, you can point them out in the comments and I will update them in time.
- The full code is here in hook.js
- The usage method is as follows:
function render() {
const [count, setCount] = useState(0);
useEffect(() = > {
const time = setInterval(() = > {
setCount(count= > count + 1);
}, 1000)
// Remove side effects
return () = > {
clearInterval(time);
}
}, [count]);
document.querySelector('.add').onclick = () = > {
setCount(count + 1);
};
document.querySelector('#count').innerHTML = count;
}
/ / bind render
Tick.render = render;
render();
Copy the code
Refer to the article
- How do React hooks work
- It’s an array, not magic
- The React principle of hooks
Communication group
Wechat group: Scan code to reply to add group.
Afterword.
If you see here, and this article is a little help to you, I hope you can move a small hand to support the author, thank 🍻. If there is something wrong in the article, we are welcome to point out and encourage each other. Thanks for reading. See you next time!
- The article warehouse 🍹 🍰 fe – code
Interested students can pay attention to my public number front-end engine, fun and material.