【问题标题】:UseRef in Suspense data fetchingSuspense 数据获取中的 UseRef
【发布时间】:2021-01-22 07:45:20
【问题描述】:

我正在尝试使用实验性的新 React 功能 Suspense for data fetching

这是我的简单 useApi 钩子(如果我正确理解 Suspense)要么返回 fetch 调用的结果,要么抛出吊杆承诺。 (稍作修改the documented example

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…">
      <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

【问题讨论】:

    标签: reactjs react-suspense use-ref


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

      1. React.Suspensechildren 元素在 throw 解决之前不会挂载。
      2. 您必须从函数体中抛出 Promise(而不是像 useEffect 这样的回调)。

      现在,您从自定义挂钩中抛出 promise,但根据 1.,组件永远不会挂载,因此当 promise 解决时,您再次抛出 promise - 无限循环。

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

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

      【讨论】:

      • 很好的解释。这为我清除了一切。谢谢!
      猜你喜欢
      • 2023-02-12
      • 2022-11-20
      • 2020-08-06
      • 2022-10-14
      • 1970-01-01
      • 2019-06-17
      • 2021-11-26
      • 2021-04-14
      • 1970-01-01
      相关资源
      最近更新 更多