如今构建的大多数 Web 应用程序都从 API 接收数据。当 获取 该数据时,我们必须考虑某些情况,在这种情况下,数据可能尚未接收。也许连接丢失了。也许端点已更改。谁知道呢。无论问题是什么,最终都是最终用户在前端得到一大堆空洞的东西。
所以我们应该对此进行说明!
处理此问题的常用方法是在应用程序中使用类似 isLoading
状态的内容。isLoading
的值取决于我们想要接收的数据。例如,它可以是一个简单的布尔值,其中返回的 true
(表示我们仍在等待数据),我们显示一个加载微调器来指示应用程序正在运行。否则,我们将显示数据。

📷 来源:Jian Wei
虽然这并不完全糟糕,但正在努力开发 React 的那些很棒的人已经实现(并且正在继续开发)一个内置的解决方案来使用名为 Suspense 的功能来处理此问题。
Suspense 或多或少地按照其名称的意思去做
您可能已经从名称中猜到了,但 Suspense 会告诉组件在满足条件之前不要渲染。就像我们用 isLoading
讨论的那样,数据的渲染会被推迟,直到 API 获取数据并将 isLoading
设置为 false
。把它想象成一个组件站在电梯里,等待到达正确的楼层再走出去。
目前,Suspense 只能用于有条件地加载使用 React.lazy()
动态渲染的组件,而无需页面重新加载。因此,假设我们有一张地图,当用户选择一个位置时,它需要一些时间来加载。我们可以用 Suspense 包裹该地图组件,并调用类似于苹果海滩球的死亡标志来显示,同时我们正在等待地图加载。然后,一旦地图加载完毕,我们就踢开球。
// Import the Map component
const Map = React.lazy(() => import('./Map'));
function AwesomeComponent() [
return (
// Show the <Beachball> component until the <Map> is ready
<React.Suspense fallback={<Beachball />}>
<div>
<Map />
</div>
</React.Suspense>
);
}
好吧。到目前为止,非常简单,我希望如此。
但如果我们想要海滩球的回退,不是针对已加载的组件,而是针对等待从 API 返回数据的情况呢?好吧,这种情况似乎非常适合 Suspense,但不幸的是,它还没有完全处理这种情况。但它会。
在此期间,我们可以使用一个名为 react-cache(包 之前 称为 simple-cache-provider)的实验性功能来演示 Suspense 在未来如何与 API 获取一起使用。
无论如何让我们将 Suspense 与 API 数据一起使用
好的,不要再“悬念”了(抱歉,忍不住)。让我们来看一个工作示例,在这个示例中,我们定义并显示一个组件作为回退,同时我们正在等待 API 返回数据。
请记住,react-cache 处于实验阶段。当我说是“实验性”的时候,我的意思就是说它真的是实验性的。甚至包描述也敦促我们不要在生产环境中使用它。
我们将构建以下内容:一个从 API 获取的用户列表。
好的,让我们开始吧!
首先,启动一个新项目
让我们从使用 create-react-app 生成一个新的 React 应用程序开始。
## Could be any project name
create-react-app csstricks-react-suspense
这将引导您的 React 应用程序。由于 Suspense API 仍在开发中,我们将使用不同的 React 版本。打开项目根目录中的 package.json 文件,编辑 React 和 React-DOM 版本号,并添加 simple-cache-provider 包(稍后我们将研究该包)。这就是它的样子
"dependencies": {
"react": "16.4.0-alpha.0911da3",
"react-dom": "16.4.0-alpha.0911da3",
"simple-cache-provider": "0.3.0-alpha.0911da3"
}
通过运行 yarn install
来安装这些包。
在本教程中,我们将构建从 API 获取数据的功能。我们可以使用 simple-cache-provider 中的 createResource()
函数在 src/fetcher.js 文件中执行此操作
import { createResource } from 'simple-cache-provider';
const sleep = (duration) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, duration)
})
}
const loadProfiles = createResource(async () => {
await sleep(3000)
const res = await fetch(`https://randomuser.me/api/?results=15`);
return await res.json();
});
export default loadProfiles
因此,这里发生了什么。sleep()
函数会阻塞执行上下文一段特定时间,这段时间将作为参数传递。然后在 loadProfiles()
函数中调用 sleep()
函数来模拟三秒(3000 毫秒)的延迟。通过使用 createResource()
来进行 API 调用,我们要么返回已解析的值(即我们期望从 API 获取的数据),要么抛出 Promise。
接下来,我们将创建一个名为 withCache
的 高阶组件,它可以在其包裹的组件上启用缓存。我们将在一个名为“withCache.js”的新文件中执行此操作。继续将其放置在项目的 src 目录中。
import React from 'react';
import { SimpleCache } from 'simple-cache-provider';
const withCache = (Component) => {
return props => (
<SimpleCache.Consumer>
{cache => <Component cache={cache} {...props} />}
</SimpleCache.Consumer>
);
}
export default withCache;
此高阶组件使用来自 simple-cache-provider 包的 SimpleCache
来启用包裹组件的缓存。我保证,当我们创建下一个组件时,我们将使用它。在此期间,在 src 中创建另一个新文件,名为 Profile.js - 这是我们将遍历从 API 获取的结果的地方。
import React, { Fragment } from 'react';
import loadProfiles from './fetcher'
import withCache from './withCache'
// Just a little styling
const cardWidth = {
width: '20rem'
}
const Profile = withCache((props) => {
const data = loadProfiles(props.cache);
return (
<Fragment>
{
data.results.map(item => (
<div key={item.login.uuid} className="card" style={cardWidth}>
<div>
<img src={item.picture.thumbnail} />
</div>
<p>{item.email}</p>
</div>
))
}
</Fragment>
)
});
export default Profile
我们这里有一个用 withCache
(我们之前创建的高阶组件)包裹的 Profile 组件。现在,无论我们从 API 返回什么(已解析的 Promise),都将作为值保存到 data
变量中,该变量被定义为将传递给具有缓存的组件(props.cache
)的配置文件数据的道具。
为了处理 API 返回数据之前应用程序的加载状态,我们将实现一个占位符组件,该组件将在 API 返回我们想要的数据之前进行渲染。
这就是我们希望占位符执行的操作:在 API 响应之前渲染回退 UI(可以是加载微调器、海滩球或其他任何东西),并且当 API 响应时,显示数据。我们还希望实现一个延迟(delayMs
),这将有利于几乎不需要显示加载微调器的情况。例如;如果数据在不到两秒钟内返回,那么加载器可能有点傻。
占位符组件将如下所示;
const Placeholder = ({ delayMs, fallback, children }) => {
return (
<Timeout ms={delayMs}>
{didTimeout => {
return didTimeout ? fallback : children;
}}
</Timeout>
);
}
delayMs
、fallback
和 children
将从 App 组件传递到 Placeholder 组件,我们将在稍后看到。Timeout
组件返回一个布尔值,我们可以使用它来返回回退 UI 或 Placeholder 组件的子元素(本例中的 Profile 组件)。
这是我们 App 的最终标记,将我们介绍的所有组件拼凑在一起,以及一些来自 Bootstrap 的装饰性标记来创建一个完整的页面布局。
class App extends React.Component {
render() {
return (
<React.Fragment>
// Bootstrap Containers and Jumbotron
<div className="App container-fluid">
<div className="jumbotron">
<h1>CSS-Tricks React Suspense</h1>
</div>
<div className="container">
<div>
// Placeholder contains Suspense and wraps what needs the fallback UI
<Placeholder
delayMs={1000}
fallback={
<div className="row">
<div className="col-md">
<div className="div__loading">
<Loader />
</div>
</div>
</div>
}
>
<div className="row">
// This is what will render once the data loads
<Profile />
</div>
</Placeholder>
</div>
</div>
</div>
</React.Fragment>
);
}
}
结束了
很不错,对吧?能够直接从 React 盒子里获得真正的回退 UI 支持真是太好了,无需狡猾的技巧或额外的库。鉴于 React 是用来管理状态的,而加载是需要处理的常见状态,所以这完全说得通。
请记住,尽管 Suspense 很棒(确实非常棒),但重要的是要注意,它仍然处于实验阶段,因此在生产应用程序中不实用。但是,由于今天有方法可以使用它,我们仍然可以在开发环境中随意尝试,所以尽情尝试吧!
那些一直在开发和使用 Suspense 的人一直在写下他们的想法和经验。以下是一些值得关注的: