1

我正在尝试使用实验性的新 React 功能Suspense 进行数据获取

这是我的简单useApi钩子(如果我正确理解 Suspense 的话)要么返回fetch调用结果,要么抛出吊杆承诺。(稍微修改了记录的示例

function useApi(path) {
  const ref = React.useRef({ time: +new Date() });
  if (!ref.current.suspender) {
    ref.current.suspender = fetch(path).then(
      data => ref.current.data = data,
      error => ref.current.error = error,
    );
  }
  if (ref.current.data) return ref.current.data;
  if (ref.current.error) return ref.current.error;
  throw ref.current.suspender;
}

我正在使用这个钩子,就像这样:

function Child({ path }) {
  const data = useApi(path);
  return "ok";
}
export default function App() {
  return (
    <Suspense fallback="Loading…&quot;>
      <Child path="/some-path" />
    </Suspense>
  );
}

它永远不会解决。

我认为问题在于它useRef并没有像预期的那样工作。

如果我用随机值初始化 ref,它不会保留该值,而是用另一个随机值重新初始化:

const ref = React.useRef({ time: +new Date() });
console.log(ref.current.time)
1602067347386
1602067348447
1602067349822
1602067350895
...

抛出suspender会导致useRef每次调用都重新初始化,这有点奇怪。

throw ref.current.suspender;

如果我删除该行useRef按预期工作,但显然 Suspense 不起作用。

我可以使它工作的另一种方法是,如果我在 React 之外使用某种自定义缓存,例如:

const globalCache = {}
function useApi(path) {
  const cached = globalCache[path] || (globalCache[path] = {});
  if (!cached.suspender) {
    cached.suspender = ...
  }
  if (cached.data) ...;
  if (cached.error) ...;
  throw cached.suspender;
}

这也使它工作,但我宁愿使用 React 本身在缓存组件特定数据方面提供的东西。

我是否遗漏了一些关于useRef应该如何使用或不应该使用 Suspense 的内容?

复制:https ://codesandbox.io/s/falling-paper-shps2

4

2 回答 2

2

让我们回顾一些事实React.Suspense

  1. 在抛出的承诺解决之前,children元素React.Suspense不会安装。
  2. 您必须从函数体中抛出承诺(而不是从回调中useEffect)。

现在,你promise从你的自定义钩子中抛出一个,但根据1.组件永远不会挂载,所以当 promise 解决时,你再次抛出 promise - 无限循环。

根据2.,即使您尝试将 promise 保存在 state 或 ref 等中,它仍然无法工作 - 无限循环。

因此,如果您想编写一些自定义钩子,您确实需要使用任何数据结构(可以全局管理 {如您的globalCache} 或由React.Suspense父级管理) ,以指示是否已抛出此特定的承诺React.Suspense(这正是Relay在Facebook 的代码库)。

于 2020-10-07T12:21:43.687 回答
2

我一直在努力解决同样的问题,但我认为实际上可以实现你想要的。我查看了 react-async 和 SWR 的实现,并注意到 react-async 实际上不会在第一次渲染时抛出,但它用于useEffect(...)启动异步操作,并结合setState触发另一个渲染然后在随后抛出承诺渲染(直到它解决)。我相信 SWR 实际上的行为是相同的,只有一点点不同。SWR 使用useLayoutEffect(回退到 useEffect 用于服务器端渲染),它有一个主要好处:没有数据的初始渲染永远不会发生。

这确实意味着父组件仍然必须处理大量数据。第一次渲染可以用来启动 Promise,但仍然必须返回而不抛出以避免无限循环。只有在第二次渲染时才会抛出承诺,这实际上会暂停渲染。

于 2021-01-13T15:19:50.210 回答