Here we go. Touch and write a hook
Hooks: 👍, 🐶. Hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: hooks: 👍, 🐶
Step 1: Introduce React and ReactDOM
Since we’re going to convert JSX to virtual-dom, we’ll leave that to Babel, and JSX will be lexically parsed by Babel to form a call to react.createElement (). The react.createElement () result is a JSX object or virtual-dom.
Since we’re rendering our demo to the DOM, we’ll introduce the ReactDOM.
import React from "react";
import ReactDOM from "react-dom";
Copy the code
Step 2: Let’s write a small demo
We define two states, count and age, that trigger an update when clicked, increasing their value by one.
In the source code useState is saved on a Dispatcher object, and mount and update get different hooks, so let’s get useState from Dispatcher for now and define Dispatcher later.
Next, define a schedule function that rerenders the component each time it is called.
function App() {
let [count, setCount] = Dispatcher.useState(1);
let [age, setAge] = Dispatcher.useState(10);
return (
<>
<p>Clicked {count} times</p>
<button onClick={()= > setCount(() => count + 1)}> Add count</button>
<p>Age is {age}</p>
<button onClick={()= > setAge(() => age + 1)}> Add age</button>
</>
);
}
function schedule() { // Each call rerenders the component
ReactDOM.render(<App />.document.querySelector("#root"));
}
schedule();
Copy the code
Step 3: Define Dispatcher
Before looking at this part, let’s clarify the relationship between Fiber, Hook and Update.
“Dispatcher” is an object that contains a number of hooks that need to be fixed from mount to update.
After calling useState, a resolveDispatcher function is called, which returns a Dispatcher object with hooks such as useState.
So let’s see what this function does, it’s a simple function, it gets current directly from the ReactCurrentDispatcher object, and it returns current as dispatcher, What is ReactCurrentDispatcher? Don’t worry, continue to look in the source code.
There is such a code in the source code, if in the formal environment, divided into two cases
- If meet
current === null || current.memoizedState === null
That means we’re in the first render, which ismount
Of which timecurrent
Is ourfiber
Node,memoizedState
Save thefiber
onhook
Which means that when you apply the first render,current fiber
It doesn’t exist. We haven’t created anyfiber
Nodes, or there are somefiber
, but the corresponding one is not built abovehook
At this point, we can think of it as being in the first render, and what we get isHooksDispatcherOnMount
- If not
current === null || current.memoizedState === null
, means that we are in the renewal phase, i.eupdate
When we get, what we get isHooksDispatcherOnUpdate
if (__DEV__) {
if(current ! = =null&& current.memoizedState ! = =null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdateInDEV;
} else if(hookTypesDev ! = =null) {
ReactCurrentDispatcher.current = HooksDispatcherOnMountWithHookTypesInDEV;
} else{ ReactCurrentDispatcher.current = HooksDispatcherOnMountInDEV; }}else {
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
Copy the code
HooksDispatcherOnMount and HooksDispatcherOnUpdate are hooks.
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useMutableSource: mountMutableSource,
useOpaqueIdentifier: mountOpaqueIdentifier,
unstable_isNewReconciler: enableNewReconciler,
};
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useMutableSource: updateMutableSource,
useOpaqueIdentifier: updateOpaqueIdentifier,
unstable_isNewReconciler: enableNewReconciler,
};
Copy the code
So the dispatcher is an object that contains all the hooks, which get different dispatchers for the first rendering and update, which call different functions, for example, if you use useState, Mount is called mountState and update is called updateState.
Now let’s write the dispatcher by hand. The Dispatcher is an object that has useState on it, and it’s represented by a self-executing function. In addition, we need to use two variables and a constant fiber
workInProgressHook
Represents the traversalhook
(because thehook
Will be saved on the linked list, need to walk through the list calculationhook
State saved on)- For simplicity, let’s define one
isMount=true
saidmount
At the time ofupdate
Set it tofalse
. - For simplicity,
fiber
Is defined as an object,memoizedState
Said thisfiber
Stored on the nodehook
List,stateNode
That’s the demo from step 2.
let workInProgressHook;// Current working hook
let isMount = true;// Whether to mount time
const fiber = {/ / fiber node
memoizedState: null./ / hook chain table
stateNode: App
};
const Dispatcher = (() = > {/ / the Dispatcher object
function useState(){
/ /...
}
return{ useState }; }) ();Copy the code
Before defining useState, let’s first look at the data structures for hook and update
Hook:
queue
: it haspending
Properties,pending
It’s also a circular list of unupdated itemsupdate
That is to say, theseupdate
innext
The Pointers are joined into a circular list.memoizedState
Indicates the current statusnext
: Point to the next onehook
Form a linked list
const hook = {/ / build the hooks
queue: {
pending: null// Update list not executed
},
memoizedState: null./ / the current state
next: null// Next hook
};
Copy the code
Update:
action
: is the function that starts the updatenext
: Connect to the next oneupdate
Form a circular list
const update = {/ / build the update
action,
next: null
};
Copy the code
Define useState in three parts:
- create
hook
Or take tohook
:- in
mount
When: callmountWorkInProgressHook
Create an initialhook
, assignmentuseState
The initial value passed ininitialState
- in
update
When: callupdateWorkInProgressHook
, get the current workinghook
- in
- To calculate
hook
Unupdated state on: traversalhook
On thepending
Linked list, calls on the linked list nodeaction
Function to generate a new state and then update ithook
The state of. - Returns the new state and
dispatchAction
The incomingqueue
parameter
function useState(initialState) {
// Step 1: create hook or fetch hook
let hook;
if (isMount) {
hook = mountWorkInProgressHook();
hook.memoizedState = initialState;// Initial state
} else {
hook = updateWorkInProgressHook();
}
// Step 2: Calculate the unupdated state on the hook
let baseState = hook.memoizedState;// Initial state
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;// The first update
do {
const action = firstUpdate.action;
baseState = action(baseState);// Call action to calculate the new state
firstUpdate = firstUpdate.next;// Calculate state using the action of update
} while(firstUpdate ! == hook.queue.pending);// Loop through the list while it is still running
hook.queue.pending = null;// Reset the update list
}
hook.memoizedState = baseState;// Assign a new state
// Step 3: Return the new status and the dispatchAction incoming queue parameter
return [baseState, dispatchAction.bind(null, hook.queue)];/ / useState returns
}
Copy the code
Next, define the mountWorkInProgressHook and updateWorkInProgressHook functions
mountWorkInProgressHook
In:mount
Create a new onehook
Object,- If the current
fiber
There is nomemoizedState
The currenthook
This is thefiber
The first one onhook
That will behook
Assigned tofiber.memoizedState
- If the current
fiber
There arememoizedState
, that will be currenthook
In theworkInProgressHook.next
behind - The current
hook
Assigned toworkInProgressHook
- If the current
updateWorkInProgressHook
In:update
Returns the currenthook
, that is,workInProgressHook
And will beworkInProgressHook
Point to thehook
Next on the list.
function mountWorkInProgressHook() {/ / call when the mount
const hook = {/ / build the hooks
queue: {
pending: null// Update list not executed
},
memoizedState: null./ / the current state
next: null// Next hook
};
if(! fiber.memoizedState) { fiber.memoizedState = hook;// The first hook is directly assigned to Fibre. memoizedState
} else {
workInProgressHook.next = hook;// If it is not the first hook, add it after the previous hook to form a linked list
}
workInProgressHook = hook;// Record the current working hook
return workInProgressHook;
}
function updateWorkInProgressHook() {/ / update call
let curHook = workInProgressHook;
workInProgressHook = workInProgressHook.next;// Next hook
return curHook;
}
Copy the code
Step 4: Define dispatchAction
-
Create update and mount queue. Pending
- If before
queue.pending
It doesn’t exist. So this one that was createdupdate
That’s the first oneupdate.next = update
- If before
queue.pending
Exists, this will be createdupdate
joinqueue.pending
In a circular list
- If before
-
Set isMount=false, assign workInProgressHook, call Schedule to update render
function dispatchAction(queue, action) {// Trigger the update
const update = {/ / build the update
action,
next: null
};
if (queue.pending === null) {
update.next = update;// Update the circular list
} else {
update.next = queue.pending.next;// The next of the new update points to the previous update
queue.pending.next = update;// Next of the previous update points to the new update
}
queue.pending = update;/ / update the queue pending
isMount = false;// mark the end of mount
workInProgressHook = fiber.memoizedState;/ / update the workInProgressHook
schedule();// Schedule updates
}
Copy the code
The final code
import React from "react";
import ReactDOM from "react-dom";
let workInProgressHook;// Current working hook
let isMount = true;// Whether to mount time
const fiber = {/ / fiber node
memoizedState: null./ / hook chain table
stateNode: App//dom
};
const Dispatcher = (() = > {/ / the Dispatcher object
function mountWorkInProgressHook() {/ / call when the mount
const hook = {/ / build the hooks
queue: {
pending: null// Update list not executed
},
memoizedState: null./ / the current state
next: null// Next hook
};
if(! fiber.memoizedState) { fiber.memoizedState = hook;// The first hook is directly assigned to Fibre. memoizedState
} else {
workInProgressHook.next = hook;// If it is not the first hook, add it after the previous hook to form a linked list
}
workInProgressHook = hook;// Record the current working hook
return workInProgressHook;
}
function updateWorkInProgressHook() {/ / update call
let curHook = workInProgressHook;
workInProgressHook = workInProgressHook.next;// Next hook
return curHook;
}
function useState(initialState) {
let hook;
if (isMount) {
hook = mountWorkInProgressHook();
hook.memoizedState = initialState;// Initial state
} else {
hook = updateWorkInProgressHook();
}
let baseState = hook.memoizedState;// Initial state
if (hook.queue.pending) {
let firstUpdate = hook.queue.pending.next;// The first update
do {
const action = firstUpdate.action;
baseState = action(baseState);
firstUpdate = firstUpdate.next;// Loop through the update list
} while(firstUpdate ! == hook.queue.pending);// Calculate state using the action of update
hook.queue.pending = null;// Reset the update list
}
hook.memoizedState = baseState;// Assign a new state
return [baseState, dispatchAction.bind(null, hook.queue)];/ / useState returns
}
return{ useState }; }) ();function dispatchAction(queue, action) {// Trigger the update
const update = {/ / build the update
action,
next: null
};
if (queue.pending === null) {
update.next = update;// Update the circular list
} else {
update.next = queue.pending.next;// The next of the new update points to the previous update
queue.pending.next = update;// Next of the previous update points to the new update
}
queue.pending = update;/ / update the queue pending
isMount = false;// mark the end of mount
workInProgressHook = fiber.memoizedState;/ / update the workInProgressHook
schedule();// Schedule updates
}
function App() {
let [count, setCount] = Dispatcher.useState(1);
let [age, setAge] = Dispatcher.useState(10);
return (
<>
<p>Clicked {count} times</p>
<button onClick={()= > setCount(() => count + 1)}> Add count</button>
<p>Age is {age}</p>
<button onClick={()= > setAge(() => age + 1)}> Add age</button>
</>
);
}
function schedule() {
ReactDOM.render(<App />.document.querySelector("#root"));
}
schedule();
Copy the code
IO /s/custom-ho…
Video explanation (efficient learning) :Click on the learning
React source code
1. Introduction and questions
2. React design philosophy
React source code architecture
4. Source directory structure and debugging
5. JSX & core API
Legacy and Concurrent mode entry functions
7. Fiber architecture
8. Render phase
9. The diff algorithm
10. com MIT stage
11. Life cycle
12. Status update process
13. Hooks the source code
14. Handwritten hooks
15.scheduler&Lane
16. Concurrent mode
17.context
18 Event System
19. Write the React mini
20. Summary & Answers to interview questions in Chapter 1