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