React source code: react

Video courses (efficient Learning) :Into the curriculum

Course Contents:

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

21.demo

Asynchronous interruptibility

  • React15: Where is the slow

Before we get to that, we need to talk about what factors cause React to be slow and need to be refactored.

Prior to React15, the reconciliation process was synchronous, also known as stack Reconciler, and because JS execution was single-threaded, pages would stall when updating time-consuming tasks without responding to high-priority tasks, such as user input. That was the CPU’s limitation.

  • The solution

How do we solve this problem? Imagine what we would do if, in everyday development, in a single-threaded environment, we ran into a time-consuming code calculation. We might first split up the task so that it could be interrupted, give up execution when other tasks come in, and when other tasks have been executed, The rest of the calculation is performed asynchronously from where it was interrupted. So the key is to implement an asynchronous interruptible scheme.

  • implementation

In the react solution, task segmentation and asynchronous execution are mentioned, and the execution can be delegated

  1. Fiber: Updates to Act15 are synchronous, and since it can’t split tasks, you need a data structure that both corresponds to the real DOM and acts as a separate unit. This is Fiber.

    let firstFiber
    let nextFiber = firstFiber
    let shouldYield = false
    //firstFiber->firstChild->sibling
    function performUnitOfWork(nextFiber){
      / /...
      return nextFiber.next
    }
    
    function workLoop(deadline){
      while(nextFiber && ! shouldYield){ nextFiber = performUnitOfWork(nextFiber) shouldYield = deadline.timeReaming <1
            }
      requestIdleCallback(workLoop)
    }
    
    requestIdleCallback(workLoop)
    Copy the code
  2. The Scheduler: With Fiber, we need to asynchronously execute these Fiber units of work using the browser’s time slice. We know that the browser has an API called requestIdleCallback that can perform tasks when the browser is idle. We use this API to perform react updates. RequestIdleCallback has browser compatibility and trigger instability issues, so we need js to implement a timescale mechanism. In React, this part is called scheduler.

  3. Lane: With asynchronous scheduling, we also need to manage the priority of each task in a fine-grained way, so that the high-priority tasks can be executed first. Each Fiber work unit can also compare the priority, and tasks with the same priority can be updated together. Isn’t it cool?

  • The resulting upper level implementation

    With this set of asynchronous interruptible mechanisms, we can implement batchedUpdates batch updates and Suspense

To get a feel for the difference between using asynchronous interruptible updates, see the following two images

Algebraic Effects

In addition to CPU bottlenecks, there are side effects related to fetching data, file manipulation, and so on. React has different device performance and network conditions. How does react handle these side effects, so that we can code best practices and run applications consistently? This requires react to be able to isolate side effects.

Q: We have all written codes to obtain data, showing loading before data acquisition, and canceling loading after data acquisition. Assuming that our device performance and network condition are good and data acquisition will happen soon, is it necessary to show loading at the beginning? How can we have a better user experience?

Take a look at the following example

function getPrice(id) {
  return fetch(`xxx.com?id=${productId}`).then((res) = >{
    return res.price
  })
}

async function getTotalPirce(id1, id2) {
  const p1 = await getPrice(id1);
  const p2 = await getPrice(id2);

  return p1 + p2;
}

async function run(){
	await getTotalPrice('001'.'002');  
}

Copy the code

GetPrice is an asynchronous method to get data, we can use async+await method to get data, but this will cause the run method that calls getTotalPrice to become an asynchronous function, this is async infectivity, so there is no way to separate the side effects.

function getPrice(id) {
  const price = perform id;
  return price;
}

function getTotalPirce(id1, id2) {
  const p1 = getPrice(id1);
  const p2 = getPrice(id2);

  return p1 + p2;
}

try {
  getTotalPrice('001'.'002');
} handle (productId) {
  fetch(`xxx.com?id=${productId}`).then((res) = >{
    resume with res.price
  })
}
Copy the code

Perform and Handle are dummy syntax. When the code executes perform, it will suspend the current function and be caught by the Handle. The Handle will retrieve the productId parameter and resume the price. Resume returns to where Perform paused and returns price, completely separating the side effects from getTotalPirce and getPrice.

The key flow here is perform suspend the function, Handle acquires the function execution, and Resume relinquishes the function execution.

This syntax is, after all, fictional, but look at the code below

function usePrice(id) {
  useEffect((id) = >{
      fetch(`xxx.com?id=${productId}`).then((res) = >{
        return res.price
  	})
  }, [])
}

function TotalPirce({id1, id2}) {
  const p1 = usePrice(id1);
  const p2 = usePrice(id2);

  return <TotalPirce props={... }>
}

Copy the code

If you replace getPrice with usePrice and getTotalPirce with the TotalPirce component, that’s the hook’s ability to separate side effects.

We know that a generator can also suspend and resume programs. However, the resumption of execution after a generator has been suspended must be transferred to the direct caller, who will continue to transfer execution along the call stack, so it is also contagious. And the generator cannot calculate the priority and sort the priority.

function getPrice(id) {
  return fetch(`xxx.com?id=${productId}`).then((res) = >{
    return res.price
  })
}

function* getTotalPirce(id1, id2) {
  const p1 = yield getPrice(id1);
  const p2 = yield getPrice(id2);

  return p1 + p2;
}

function* run(){
	yield getTotalPrice('001'.'002');  
}

Copy the code

Decoupling side effects is very common in the practice of functional programming, such as Redux-Saga, where side effects are separated from saga and you don’t handle side effects yourself, just making requests

function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED".user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED".message: e.message}); }}Copy the code

React doesn’t support Algebraic Effects, but it has Fiber updates, and then gives the browser control over how to schedule it.

Suspense is also an extension of this concept, and it will feel a bit better when you see the code for Suspense in detail. Let’s look at an example

const ProductResource = createResource(fetchProduct);

const Proeuct = (props) = > {
    const p = ProductResource.read( // Write asynchronous code synchronously!
          props.id
    );
  return <h3>{p.price}</h3>;
}

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>} ><Proeuct id={123} />
      </Suspense>
    </div>
  );
}
Copy the code

As you can see, ProductResource. Read is written completely synchronously, separating the part that fetched the data completely out of the Proeuct component. In the source code, ProductResource. Due to scheduler’s presence, the Scheduler can catch the promise, suspend the update, and return the execution when the data is retrieved. ProductResource can be localStorage or even redis, mysql and other databases. That is, Component is service. Server Component may appear in the future.