Suspense & Lazy in React

JavaScript

在用 React 处理业务的过程中,经常会遇到这样的场景:某一个 UI 需要等待网络请求来展示,在等待的过程中,需要显示 Loading 界面,并在请求完成后,显示真正的 UI。这种情况,和按需加载模块的行为非常类似。既然 React.Suspense + React.lazy 可以组合用于按需加载模块时候的 UI 展示,那么是否可以使用同样的组合来完成类似等待网络请求的 UI 显示呢?答案是肯定的。下面给出一个示例代码:

function sleep(time) {
  return new Promise(resolve => setTimeout(resolve, time));
}

const fakeFetch = () => sleep(1000).then(() => "finished!");

const Component = React.lazy(() =>
  fakeFetch().then(text => ({
    default: () => <div>{text}</div>
  }))
);

const App = () => (
  <div className="App">
    <React.Suspense fallback={<div>loading...</div>}>
      <h1>Hello World</h1>
      <Component />
    </React.Suspense>
  </div>
);

如此一来,在 Promise 没有返回的时候,组件会显示 <div>loading...</div>。而等到 Promise resolve 之后,就会显示真正的 UI。

几点说明:

  1. React.lazy 本身是为 import() 设计的,所以在 Promise 返回的时候,需要将组件放到 default 属性下面,保持和 import() 的行为一致;
  2. React.SuspenseReact.lazy 的组合,本质上内部是使用了 throw + componentDidCatch 的方式进行实现的,因而如果不使用 React.lazy,直接在组件内 throw Promise,也可以达到类似的效果:
const fakeFetch = fn => sleep(1000).then(() => fn("finished!"));

let data = "before";
const Component = () => {
  if (data === "before") {
    throw fakeFetch(newData => {
      data = newData;
    });
  }
  return <div>{data}</div>;
};

const App = () => (
  <div className="App">
    <React.Suspense fallback={<div>loading...</div>}>
      <h1>Hello World</h1>
      <Component />
    </React.Suspense>
  </div>
);