React source code analysis 18 (6) ------ Implementing useState

Summary

In the previous article, we have implemented functional components. At the same time, rendering can be performed normally through render.

Through the previous articles, beginWork and completeWork already have basic frameworks. Now we can implement useState.

Before implementation, we need to modify our index.js file first:

import jsx from '../src/react/jsx.js'
import ReactDOM from '../src/react-dom/index'
import {
    
     useState } from './react-dom/filberHook.js';

const root = document.querySelector('#root');

function App() {
    
    
  const [name, setName] = useState('kusi','key');
  window.setName = setName;
  const [age, setAge] = useState(20)
  window.setAge = setAge;
  return jsx("div", {
    
    
    ref: "123",
    children: jsx("span", {
    
    
      children: name + age
    })
  });
}

ReactDOM.createRoot(root).render(<App />)

Since our article will not implement React's event mechanism, we first mount the setState method on the window for debugging. With the foundation in place, we now start implementing useState.

1.renderWithHook

Before implementing it, let’s think about a problem first. When we implemented the beginWork mechanism before, we did it for compatibility with function components. When obtaining the child FilberNode, the function component is directly called to get the return value.
So if the function is called directly, has the Hook we written in the function been called?
So let’s break this part out:

function updateFunctionComponent(filberNode) {
    
    
  const nextChildren = renderWithHook(filberNode);
  const newFilberNode = reconcileChildren(nextChildren);
  filberNode.child = newFilberNode;
  newFilberNode.return = filberNode;
  beginWork(newFilberNode)
}

When updating the function node, get the return value of function execution through renderWithHook:
Then what else should we do in renderWithHook besides getting the return value of function execution? ?

It is worth noting here that we know that through setState, the function component will re-perform rendering. Here, we divide the execution of the function into two types: the first mount and the subsequent update.

The process of executing useState is divided into two types, one is useState under mount, and the other is useState under update. OK, now we use a flag to represent these two states and change it in renderWithHook.

let hookWithStatus;
let workInPropgressFilber = null;

export const renderWithHook = (filberNode) => {
    
    
  if(filberNode.child){
    
    
    //更新
    hookWithStatus = 'update'
  }else{
    
    
    //mount
    hookWithStatus = 'mount'
  }
  workInPropgressFilber = filberNode;
  const nextChildren = filberNode.type();
  return nextChildren;
}

2. Implement mountState and Hook structures

Now after we finish executing beginWork, we will execute renderWithHook, and the hookWithStatus flag will be changed after execution. Then there is the calling function itself.
So now we implement two different useStates based on this flag:

export const useState = (state) => {
    
    
  if(hookWithStatus === 'mount'){
    
    
    return mountState(state)
  }else if(hookWithStatus === 'update'){
    
    
    return updateState(state)
  }
}

That is, when the page is rendered for the first time, to execute the content in the function component, we need to call mountState. Now we implement mountState.

Before implementation, let's first talk about how to store Hooks in components in React. In React, different Hooks are stored through linked lists. Now let's define the structure of Hook:

It has three properties. memoizedState represents the stored state value, updateQueue represents the value that needs to be updated, and next represents the next hook pointed to.

class Hook {
    
    
  constructor(memoizedState, updateQueue, next){
    
    
    this.memoizedState = memoizedState
    this.updateQueue = updateQueue
    this.next = next;
  }
}

So in mountStaet, we need to implement this linked list structure:
Here we define a headHook pointing to the outermost hook, and workinProgressHook pointing to the current hook.

function mountState(state) {
    
    
  const memoizedState = typeof state === 'function' ? state() : state;
  const hook = new Hook(memoizedState);
  hook.updateQueue= createUpdateQueue()
  if(workInPropgressHook === null){
    
    
    workInPropgressHook = hook;
    headHook = hook;
  }else{
    
    
    workInPropgressHook.next = hook;
    workInPropgressHook = hook;
  }
  return [memoizedState]
}

Now we can take a look at the structure of HOOK:

Insert image description here
It can be seen that it is a linked list structure, and memoizedState saves the initial value of setState.

3. Implement dispatch update

Now after the mount stage, we already have a basic Hook linked list. Now if I call setState under window, nothing will happen.

So we need to implement the setState method, but calling the setState method must be updated, so we modify the updateContainer method in beginWork and expose it:

function updateContainer(root, element) {
    
    
  const hostRootFilber = root.current;
  const update = createUpdate(element);
  hostRootFilber.updateQueue = createUpdateQueue()
  enqueueUpdate(hostRootFilber.updateQueue, update);
  wookLoop(root,hostRootFilber)
}

export const wookLoop = (root,hostRootFilber) => {
    
    
  if(!hostRootFilber){
    
    
    hostRootFilber = root.current
  }
  beginWork(hostRootFilber);
  completeWork(hostRootFilber);
  root.finishedWork = hostRootFilber;
  console.log(root)
  commitWork(root)
}

In this way, I can call wookLoop in the hook mechanism. Now we implement dispatch:

function disaptchState(filber, hook, action) {
    
    
  const update = createUpdate(action);
  enqueueUpdate(hook.updateQueue, update);
  workUpdateHook = hook;
  wookLoop(filber.return.stateNode)
}

The dispatchState method passes in the current filterNode, the corresponding hook, and the action that needs to be updated.
At the same time, we will mark the hook that is ready to be updated.

So in mountState:

function mountState(state) {
    
    
  const memoizedState = typeof state === 'function' ? state() : state;
  const hook = new Hook(memoizedState);
  //其他代码。。。
  const disaptch = disaptchState.bind(null,workInPropgressFilber,hook)
  return [memoizedState,disaptch]
}

We pass in the parameters required for dispatch and only release the action to the outside. In this way, the dispatch method is implemented.

4. Implement the updateState method

After we implement the above process, if setState is called on the console. Then the workLoop will be triggered, and beginWork will be executed again.
After entering renderWithHook at this time, it will no longer go to mountState, but to updateState.

In updateState, what we have to do is not very complicated. We only need to traverse the Hook list from the beginning. If it is a Hook that is marked updated, return the updated content. If not, just return its memoizedState normally.

function updateState(state) {
    
    
  if(currentHook === workUpdateHook){
    
    
    const newHook = new Hook(workUpdateHook.updateQueue.shared.pending.action)
    newHook.updateQueue = createUpdateQueue();
    const disaptch = disaptchState.bind(null,workInPropgressFilber,newHook)
    currentHook = currentHook.next;
    const result = [workUpdateHook.updateQueue.shared.pending.action,disaptch];
    return result;
  }else{
    
    
    let result = currentHook.memoizedState;
    const disaptch = disaptchState.bind(null,workInPropgressFilber,currentHook)
    currentHook = currentHook.next;
    return [result,disaptch]
  }
}

So this is why, in React, Hook cannot be used in conditional statements, if the Hook linked list generated by your mountState will change. Then in updateState, when traversing the linked list, value misalignment will occur.

OK, the useState method has been implemented here.

Guess you like

Origin blog.csdn.net/weixin_46726346/article/details/132275650