如何将从远程 API 获取的初始数据与 React.Suspense 集成?

Posted

技术标签:

【中文标题】如何将从远程 API 获取的初始数据与 React.Suspense 集成?【英文标题】:How to integrate initial data fetching from a remote API, with React.Suspense? 【发布时间】:2021-11-09 21:21:39 【问题描述】:

我正在现有样板之上构建一个新的 React 应用程序。它使用延迟加载,结合 React.Suspense。

问题是,就像在大多数 React 应用程序中一样,我需要在每次应用程序加载时从服务器获取一些初始元数据。我们称之为“getAppMetaData”。

那么问题是什么?问题是,虽然 getAppMetaData 处于待处理状态,但我需要提供一些加载器/微调器。这正是 React.Suspense 所做的:它显示了“后备”用户界面。当然,我可以运行一个单独的加载器(实际上可以与回退 UI 相同),但这会产生 UX 问题,加载器的动画在程序之间“重新启动”。

那么,问题是,我如何将其他异步操作“集成”到此暂停中?简而言之:“我的后备 UI 已经显示,而块(来自延迟加载)已加载 - 那么我如何让它也等待 getAppMetaData?”

这是我的路由器:

<ErrorBoundary>
     <Suspense fallback=<div className=styles.loader><Loader /></div>>
        <Switch>
          <ProtectedRoute exact component=Home path="/">                    
          </ProtectedRoute>    
             <Route path="/lesson">
               <Lesson></Lesson>
             </Route>    
            <Route exact path="/login">
              <Login />
            </Route>
               <Route path="/about">
            <About />
            </Route>
            <Route path="*">
              <NotFound />
          </Route>
        </Switch>
      </Suspense>
    </ErrorBoundary>

React 文档指出,应该为此使用 Relay 库,但我不想为我的 API 调用使用任何特殊库,只是为了克服这个简单问题。它还指出:

如果我不使用 Relay 会怎样?如果你今天不使用 Relay,你可能 必须等待才能真正在应用中尝试 Suspense。迄今为止, 这是我们在生产中测试的唯一实现,并且是 有信心。

我所需要的只是将一个小的初始 API 调用集成到这个过程中。如何做呢? 任何建议将不胜感激。

【问题讨论】:

Suspense 只是一个组件,它捕获一个 Promise 并显示一个回退直到 Promise 被解决,所以你使用 Promise 加载数据并抛出它,这样 Suspense 组件就可以捕获它并显示数据加载时微调器 我明白了,但是我到底应该把这段代码放在哪里呢?我如何确保它被悬念抓住了?如果我有一些组件位于 Suspense 树中,我该如何实际操作呢?如果我从 useEffect 抛出一个承诺,我会得到一个未捕获的错误 【参考方案1】:

我会将 Suspense 的子组件移动到一个新组件中并从该组件中读取数据。

数据将使用自定义函数fetchData 加载,该函数将创建promise 并返回一个包含read 方法的对象,如果数据未准备好,该方法将抛出promise。

function fetchData() 
  let status = 'pending';
  let result;

  const promise = fetch('./data.json')
    .then(data => data.json())
    .then(r => 
      status = 'success';
      result = r;
    )
    .catch(e => 
      status = 'error';
      result = e;
    );

  return 
    read() 
      if (status === 'pending') 
        throw promise;
       else if (status === 'error') 
        throw result;
       else if (status === 'success') 
        return result;
      
    
  ;


const dataWrapper = fetchData();

function AppBody() 
  const data = dataWrapper.read();

  // you can now manipulate the data
  return (
    <Switch>
      <ProtectedRoute exact component=Home path="/"/>                    
      <Route path="/lesson" component=Lesson />
      <Route exact path="/login" component=Login />
      <Route path="/about" component=About />
      <Route path="*" component=NotFound />
    </Switch>
  )


function App() 
  return (
    <ErrorBoundary>
     <Suspense fallback=<div className=styles.loader><Loader /></div>>
       <AppBody/>
     </Suspense>
    </ErrorBoundary>
  );

这是stackblitz example

这段代码的灵感来自react documentationcodesandbox

【讨论】:

哇,这很好用。我一定会学习这段代码并理解其中的机制。谢谢。 您只需要了解Suspense 组件的工作是捕获promise 并显示一个备用组件,直到promise 被解决。 你将如何从这个创建一个自定义的钩子,它可以在任何组件中使用?(都在同一个 Suspense 树下)。可以让我从组件中调用“useSuspense(somePromise)”的东西。 最简单的方法是在自定义钩子中使用useMemo[] 来创建promise 并使用read 方法返回对象。更复杂的方法是结合useState(用于存储承诺)和useMemo 使用 read 方法构建对象,使用此方法您可以通过使用新承诺更新状态来刷新数据。 谢谢。你能解释一下我到底需要记住什么吗?

以上是关于如何将从远程 API 获取的初始数据与 React.Suspense 集成?的主要内容,如果未能解决你的问题,请参考以下文章

React Flux 从远程服务获取 Api 数据

如何使用 api 的数据初始化状态

将从 api 接收到的数据格式化为数组 React Axios

如何将从 API 获取的数组分配给 Vue.js 中的数据属性?

如何使用 VueJS 和 axios 将从 API 获取的数据填充到 Laravel 中的选择选项中?

如何重新初始化 React 钩子