包装一个 React 钩子并保留其类型

Wrap a React hook and retain its types

我想包装 React 查询以便显示错误。如何键入 args、通用和 return 类型以镜像 useQuery?

export function useRelocaterQuery<T>(...args: any): Foo {
                               // ^            ^     ^ type this to mirror useQuery()

  const { enqueueSnackbar } = useSnackbar();

  const query = useQuery(...args);

  if (query.error) {
    enqueueSnackbar("some generic message");
  }

  return query;
}

我看到您可以使用中间函数 (wrap) 对常规函数执行此操作,但我不知道如何将其扩展为挂钩。

const wrap = <T extends Array<any>, U>(fn: (...args: T) => U) => {
  return (...args: T): U => fn(...args)
}

因为 useQuery 有 4 个泛型,所以 useQuery 的每个低级包装器也需要有相同的泛型。 useQuery 本身也有 3 个函数重载,所以你必须选择一个结构。最简单的解决方案是使用对象结构并基本上复制 react-query 为选项所做的任何事情。 return类型一般可以推断出来,或者需要使用UseQueryResult:

export function useRelocaterQuery<
  TQueryFnData = unknown,
  TError = unknown,
  TData = TQueryFnData,
  TQueryKey extends QueryKey = QueryKey
>(
  options: UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>
): UseQueryResult<TData, TError> {

  const { enqueueSnackbar } = useSnackbar();

  const query = useQuery(options);

  if (query.error) {
    enqueueSnackbar("some generic message");
  }

  return query;
}

作为 side-note,我通常不认为像这样的抽象是首选。首先,调用 enqueueSnackbar 可能违反了 react 渲染函数必须是纯函数的规则,因为它在渲染期间显然会产生 side-effect。所以你必须把它放在 useEffect 中,至少可以避免 React 18 及以后的并发特性出现问题。

除此之外,还有更好的方法来处理一般错误。例如,要显示错误提示,我可以在 QueryCache 上推荐 the global onError handlers

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error) =>
      toast.error(`Something went wrong: ${error.message}`),
  }),
})

如果您需要关闭来自钩子的 enqueSnackbar,您需要在应用程序的渲染函数中创建 QueryClient,因此您需要通过将其放入状态来使其稳定

function App() {
  const { enqueueSnackbar } = useSnackbar();

  const [queryClient] = React.useState(() => {
    return new QueryClient({
      queryCache: new QueryCache({
        onError: (error) =>
          enqueueSnackbar("some generic message");
      }),
    })
  })
}