React's Suspense function, simply put, allows component rendering to seamlessly "hover" (suspense) when an asynchronous operation is required, and then continue seamlessly when the asynchronous operation has a result.

The asynchronous operations mentioned here can be divided into two categories:

  • Load code asynchronously
  • Load data asynchronously

It's easy to identify. When writing programs, the tossing is nothing more than "code" and "data".

Why does load code asynchronously?

Originally, the code can be packaged into one file, but when the amount of code is very large, and not all the code is used when the page is loaded, pulling them into the only package file, except for the simplicity of the packaging process, there is no benefit. Therefore, in order to squeeze performance, we must consider packaging the code into several files, so that each packaged file can be relatively small and loaded as needed. This is a good idea, but it's not enough for every developer to implement this mechanism, so React uses Suspense to provide a unified and seamless code splitting (Code Splitting) and asynchronous loading method, in v16.6.0 Such a Suspense function is implemented.

Everyone is interested in playing by themselves. This kind of Suspense is not the focus of today's discussion. Today, we will talk about Suspense of "asynchronously loading data", that is, using Suspense to call server APIs and other operations.

According to React's official roadmap, using Suspense for data loading will not be released until the middle of this year (2019). If you see this article late, it may have already been released.

What I want to talk about today is that Suspense does data loading, which is a little contradictory to the asynchronous rendering of React .

In the previous Live "In-depth Understanding of the New Features of React v16", I said that starting from React v16, the life cycle of a component can be divided into two phases: the reconciliation phase and the commit phase. The life cycle function in the reconciliation phase may be interrupted due to the design characteristics of Fiber, and after being interrupted, it will be called again; and once the commit phase starts, it will never be interrupted. The dividing line between the reconciliation phase and the commit phase is the render function. Note that the render function itself belongs to the reconciliation phase.

For example, a component is rendered and executed into the render function. At this time, the user suddenly enters something in an input control. At this time, React decides to prioritize the key events in the input control, which will interrupt the component. The rendering process, that is, no matter what the render returns, the rendering process will stop there, stop drawing, and concentrate on processing the input control. Wait until the things over there are processed, and then render the component, but then restart from the original position, it must be unreliable, because the key event processing just now may have changed some states, in order to ensure absolute reliability, React decided ...let's go through it all over again, so, re-adjust getDerivedStateFromProps, shouldComponentUpdate and then call render.

See, the lifecycle functions before render will be called, and because this "interruption" is completely unpredictable, now requires all lifecycle functions in the render phase not to do side effects. Operation .

What are side effects? That's what pure functions shouldn't do.

What is a pure function? That is, in addition to returning the result according to the input parameter, it does not do any more operations.

If a function modifies global variables, it is not a pure function; if a function modifies the state of a class instance, it is not a pure function; if a function throws an exception, it is not a pure function; if a function accesses the server through AJAX API, that's not a pure function.

Take accessing the server API as an example. If the life cycle function of the reconciliation phase performs the AJAX operation to access the server API, then it is very likely to generate continuous access to the server, because the reconciliation phase will be interrupted and repeated under asynchronous rendering. Execute.

class Foo extends React.Component {
  shouldComoponentUpdate() {
    callAPI(); // 你只看到了一行代码,但是可能会被多次调用
    return true;
  }

  render() {
    callAPI(); // 你只看到了一行代码,但是可能会被多次调用

    return JSX;
  }
}

Again, all lifecycle functions in the reconciliation phase should not do operations with side effects, these functions must be pure functions.

So, now the question is, will using Suspense to obtain data will violate this rule?

Although the API of Suspense has not yet been determined, the code form is still clear, and the demo version of react-cache is used to show it.

import React, { Suspense } from "react";
import { unstable_createResource as createResource } from "react-cache";

// 模拟一个AJAX调用
const mockApi = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve("Hello"), 1000);
  });
};

const resource = createResource(mockApi);

const Greeting = () => {
  // 注意看这里,这里依然是在render阶段,可能会被重复调用哦!
  const result = resource.read();

  return <div>{result} world</div>;
};

const SuspenseDemo = () => {
  return (
    <Suspense fallback={<div>loading...</div>}>
      <Greeting />
    </Suspense>
  );
};

export default SuspenseDemo;

It is interesting here. Suspense is used to obtain data. Since the data is used in the render function (or in the function type component as in the above example), the process of obtaining data must be in the reconciliation phase. However, the process of obtaining data is If you want to call AJAX, AJAX is a side effect operation. Isn't this contradicting the "no side effect operation in the reconciliation phase"?

It's a bit contradictory indeed.

The React developers explain this like this:

image.png

In short, the requirements are reduced, and the operation in the reconciliation phase only needs to be "idempotent" (indempotent).

The so-called idempotency means that one call produces the same result as N calls.

Let's take an example.

// 这是纯函数,没有副作用
function foo1(a, b) {
  return a + b;
}

// 这不是纯函数,有副作用,而且不幂等
function foo2(a, b) {
  sendAjax(a + b);
  return a + b;
}

// 这不是纯函数,有副作用,但是——幂等
let called = false;
function foo3(a, b) {
  if (!called) {
    sendAjax(a + b);
  }
  called = true;
  return a + b;
}

The function foo3 above does have side effects. However, the code is used to cleverly prevent it. Only the first call will send AJAX, and the subsequent calls will not send AJAX. In this way, no matter how many times it is called, the effect will be the same. It is "idempotent".

Although idempotent is not as pure as pure functions, it is good enough, at least for a framework like React that cannot be "pure", which is the best result.

The unstable_createResource function of the demo version of react-cache accepts a function that returns a Promise as a parameter, and the obtained Promise will be cached. Therefore, although it will be interrupted and repeated many times, if resource.read() returns a result, Then the returned results are the same, and the "idempotent" effect is achieved.

In this way, the conflict is resolved.

To sum up, in fact, we need to " all life cycle functions in the reconciliation phase do not do side-effect operations, these functions must be pure functions "This requirement is changed, and changed to" in the reconciliation phase of all life cycle Periodic functions should all be idempotent ".

Although React is often said to say "functional programming", React is really innately far away from "functional programming" that advocates pure functions, including Hooks, which on the surface advocate functional components, which seems to be a function of functional programming. Hooks are a step forward, but all Hooks are stateful, so how can they be pure functions?

Of course, it is not necessary to pursue pure functions like cleanliness. Since "idempotent" can solve the problem, we are also happy to see it.

It confirms the old saying: as long as you lower the standard, you will find that the world suddenly opens up :)

PS If the background knowledge in this article is unclear, just go to my previous Live, I have said all the knowledge points that should be said.

"A Quick Look at React's New Suspense and Hooks"

"In-depth understanding of the new features of React v16"

"Help you understand React"


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。