1. Hook object data structure
A function component can have more than one hook, so how to save?
Function component Fiber data structure
let fiber = {
memoiezdState:null.// memoiezdState is the memoiezdState of the function component, unlike the memoiezdState of the class component, which is stored in a separate hook object
stateNode:App,
updateQueue:null// The updateQueue of the function component is a linked list of hooks that need to execute useEffect
}
Copy the code
Hook object data structure
let hook = {
queue: {pending:update // This is the last update in the useState updateNumber list
},
merizedState:null// Save the state variable in useState as a small class component
next:null// Execute the next hook object in the function component
}
/ / create hooks, fiber. MemoiezdState = firstHook, then create hook objects behind. Next, according to the implementation of the time is fiber. The memoiezdState this list to order.
Copy the code
Update data structure of hook
let update = {
action:function/state;// Stores the new state of updateNum
next:null// Next update
}
Copy the code
2. Minimal version of useState implementation
In the Render phase, the function of the function component will be re-called to diffChildren, so useState will be used. Therefore, both the updateNum and mount phases will enter the Render phase and the function will be re-executed
The other hooks implementations are just hook.memerized and fired at different times
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, Initial-scale =1.0"> <title>useState's minimal implementation </title> </head> <body> </body> <script> let mouted = true let workingProgressHook = null let fiber = { stateNode:App, updateQueue:null, memorizedState:null, } function App(){ const [number,updateNumber] = useState(0) const [status,updateStatus] = useState(false) console.log('number',number); console.log('status',status); console.log('mouted',mouted); return { onclick(){ updateNumber(number=>number+1) updateNumber(number=>number+1) updateNumber(number=>number+1) }, trigger(){ updateStatus(status=>! Status)},}} function run(){fibre.memorizedState = fibre.statenode (); Mouted = false return app} function useState(baseState){let hook = null if(! mouted){//! The current hook already exists. Next}else{newState = baseState hook ={newState = baseState hook ={newState = baseState hook ={newState = baseState hook ={ queue:{ pending:null }, memorizedState:baseState, next:null } if(! fiber.memorizedState){ fiber.memorizedState = hook }else{ workingProgressHook.next = hook } workingProgressHook = BaseState = hook. MemorizedState if(hook. Queue.pending){let firstUpdate = hook do{ const action = firstUpdate.action baseState = action(baseState) firstUpdate = firstUpdate.next }while(firstUpdate! } hook. Queue. Pending = null// Delete hook. MemorizedState = baseState return [baseState,dispatchAction.bind(null,hook.queue)] } //! Function dispatchAction(queue,action){function dispatchAction(queue,action){let update = {action, Next = queue.pending. Next = queue.pending. Next = update}else{ Update. Next =update} queue.pending =update // start render run()} window.app = run() </script> </ HTML >Copy the code
3.useEffect/useLayoutEffect
The memorizedState of the useEffect hook corresponds to the effect object. The data structure of the effect object is as follows
const effect: Effect = {
tag,
create,// The callback function
destroy,// Unload the function
deps,
// Circular
next: (null: any),
};
Copy the code
Meanwhile, these effects will be stored in the linked list of function component Fiber’s update value this time. However, we will execute this effect only when dePS changes, and this function Fiber will be added to the Effect -list in the COMMIT phase
function mountEffectImpl(fiberFlags, hookFlags, create, deps) :void {
const hook = mountWorkInProgressHook();/ /! The update phase is the Hook created in the memorizedState list of the current Fiber or the Hook created in the mount phase
const nextDeps = deps === undefined ? null : deps;/ /! dependency
currentlyRenderingFiber.flags |= fiberFlags;/ /! FiberFlags is useEffect/layputeffect and you add this tag on fiber to distinguish between the two
hook.memoizedState = pushEffect(/ /! The memoizedState of useState is the value of State and the memoizedState of useEffect is the Effect data structure
HookHasEffect | hookFlags,
create,
undefined,
nextDeps,
);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) :void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if(currentHook ! = =null) {
const prevEffect = currentHook.memoizedState;/ /! The effect object of the last useEffect
destroy = prevEffect.destroy;/ /! The destruction function of the effect object last useEffect
if(nextDeps ! = =null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {/ /! If the dePS is the same, we will add this effect to fiber's UDpateQueue but will not execute it because the tag only wears hookFlags
pushEffect(hookFlags, create, destroy, nextDeps);
return; } } } currentlyRenderingFiber.flags |= fiberFlags; hook.memoizedState = pushEffect(/! Only deps is not the same We'll add the effect to the fiber udpatequeue effect because the tag is the need to perform in the la HookHasEffect | hookFlags, HookHasEffect | hookFlags, create, destroy, nextDeps, ); }Copy the code
If the dePS is not changed, we should push the effect into fiber. Update Ue. Because we can not miss one of the effects in order
UselayoutEffect is just the second tag with different logic except that it is executed at a different time during the commit phase
4.useRef
A REF can be used as a data structure and a lifecycle
Hostcomponent classcomponent, forwardRef can assign ref attribute, including forwardRef just hand down, the ref does not perform the logic of the ref
UseRef’s memoizedState of the hook object is an object. {current: Value} returns this object, so this object is stored on the memoizedState of the hook object
Ref has operations in both render and Commit phases
Render phase:
If the ref is not the same in the first rendering or update phase, we will add the ref tag to the function fiber
The commit phase:
The first is the ref removal phase: commitmutationEffect
In the mutationEffects function, corresponding to the update phase, if the two refs are different, we remove the previous ref(i.e. Ref.current =null) and if a fiber is removed, its ref and its child fiber are all removed
Next comes the ref assignment stage, the commitlayouteffects function
For each fiber type we assign ref to the new instance
5.useCallback&useMemo
The useMemo: Hook object’s memorizedState stores this value and dependencies
Update usememo retrieves the dePS of the hook’s memorizedState and compares it to the dePS of the hook’s memorizedState
UseCallback is similar: the hook object’s memorizedState stores this function and dependencies