Author: Bitter melon Mei

Pre-knowledge:

2. React hooks are implemented sequentially and cannot be nested

Question:

1. How do you implement sequentially which are hooks?

Use useState to update different objects. How do react hooks distinguish which objects to update?

3. Update state several times at the same time, how to ensure synchronization?

The working principle of

Take the simplest counter as an example:

export function Demo() { const [count, updateCount] = useState(1) const [name, updateName] = useState('a') const handleClick = () => { updateCount(count => count +1) updateCount(count => count +1) } const handleChange = () => { updateName(name => name +'a') updateName(name => name +'b') } return (<> </p> <button onClick={handleClick}>ADD</button> <button onClick={handleChange}>Change</button> </>)}Copy the code

By defining useState, useState returns the value of [count, updateCount], which is always the latest value. UpdateCount is the method used to update that count. Changes to updateCount trigger component rerender. The same applies to name updates. So the React Hooks workflow looks like this:

1. Generate an update in some way that causes the component to render again.

UseState returns the updated result when the component render

The update is divided into mount and update stages:

1. Assign an initialValue to the mount to generate an update of the mount

2. Increment the value by 1 by clicking the button to generate an update

Start implementing a setState

React hooks use a one-way list of hooks, so you need a place to store the hooks list. That place is the Fiber object. Define a simple Fiber object as follows:

Let fiber = {memoizedState: null, // Save the component corresponding to the hooks list, each list node is a hook object stateNode: Demo // point to the function component}Copy the code

Let fiber’s stateNode execute the function component so that it can be re-executed by calling fiber.statenode (); Record information from the first hook to the last hook in linked list form with Fiber. memoizedState

Since you have a linked list, you need to have a pointer to which hook is executing under the current working stream, so define a variable workInProgressHook as a pointer.

Define a variable isMount to distinguish between mount and update phases

Let workInProgressHook = null //Copy the code

Then define a scheduler schedule to execute the component rerender, and each time you re-execute, reset the workInProgressHook to the first hook.

function schedule(){
  workInProgressHook = fiber.memoizedState;
  fiber.stateNode()
}
Copy the code

At this point, let’s simply write the useState code as follows:

Function useState(initialState) {// The hook used by useState is assigned to let hook; if (isMount) { // ... } else {//... Return [state, setStateAction], return (state, setStateAction), return (state, setStateAction), return (state, setStateAction),Copy the code

Define the hook object and initialize it so that Fiber. memoizedState points to the first useState, then the second hook comes in (execute the second useState) and the first hook points to the second

The code is as follows:

function useState(initialState){ let hook if (isMount) { // ... Mount when the need to generate hook hook object = {memoizedState: initialState, next: null} the if (! fiber.memoizedState){ fiber.memoizedState = hook }else{ workInProgressHook.next = hook } workInProgressHook = hook } else { // ... Let baseState = hook. MemoizedState; //TODO: handle updates //... return [baseSate,dispatchAction] }Copy the code

Since react Hook is rerender for each update, we need to define an update object to store the information of each update. SetCount is called three times as follows:

 const handleClick = () => {
    updateCount(count => count +1)
    updateCount(count => count +1)
    updateCount(count => count +1)
  }
Copy the code

Simulate the react to the processing of update operations, these updates will be the same with together, through the loop in the form of a singly linked list, using the ring, is the purpose of the react on the concept of update operations have priority, circular linked list can be convenient to any implementation is a higher priority to update location, and temporarily abandon the update of low priority. Define the update structure as follows:

Const update = {action,// update function count => count +1 next: nullCopy the code

Q: When do I update links to objects? Where is the updated information stored?

A: Store the updated information in the corresponding hook object. Add a queue attribute to the hook object. Queue’s pending pointer points to the updated list. These updates are linked when dispatchAction is called.

So we update the hook object structure, the updated hook object structure:

 hook = {
      queue: {
        pending: null
      },
      memoizedState: initialState,
      next: null
  }
Copy the code

A circular one-way chain indicates intent:

Define the dispatchAction method as follows:

Function dispatchAction (queue,action) {function dispatchAction (queue,action) { Update const update = {action, next: If (queue.pending === null) {update.next = update; } else { update.next = queue.pending.next; queue.pending.next = update; } queue.pending = update; schedule(); // React starts scheduling updates}Copy the code

Schematic diagram of series of circular lists:

Each call of dispatchAction concatenates these update objects, causes the component to re-execute through Schedule, and then performs the hook mounted updates in useState

function useState(initialState) { let hook; if (isMount) { hook = { queue: { pending: null }, memoizedState: initialState, next: null } if (! fiber.memoizedState) { fiber.memoizedState = hook; } else { workInProgressHook.next = hook; } // change the progresshook pointer to workInProgressHook = hook; } else {// update hook = workInProgressHook; WorkInProgressHook = workInProgressHook. Next; } let baseState = hook.memoizedState; if (hook.queue.pending) { let firstUpdate = hook.queue.pending.next; do { const action = firstUpdate.action; baseState = action(baseState); firstUpdate = firstUpdate.next; } while (firstUpdate ! == hook.queue.pending) hook.queue.pending = null; } hook.memoizedState = baseState; return [baseState, dispatchAction.bind(null, hook.queue)]Copy the code

Conclusion:

Connect to the hooks object as a linked list, store the linked list information in memoizedState of the Fiber object for update, the connection will be updated every time dispatchAction is called, the update information is stored in the queue of the corresponding hook object, Schedule then re-executes the function component, enters useState, checks the queue of the corresponding hook, and executes. The result is returned.

Finally: Clone a copy of react source code, find reactFiberlinks.old.js, search useState, and see that useState exists in multiple HooksDispatcher, find the corresponding mountState and updateState methods. You can see that this is consistent with the simple useState implementation.

React:

1. React Hooks do not use the isMount variable, use different dispatcher at different times. React: HooksDispatcher

React Hooks have optimizations that bypass updates in mid-stream.

React Hooks trigger batchedUpdates. When updateNum is triggered three times in Click, React Hooks trigger three updates, React Hooks trigger only one.

4. React Hooks have a looped list, but do not take advantage of the looped structure.

Ode to refer to the article: @ wizard card www.yuque.com/liangxincha… React.iamkasong.com/process/fib…