如何使用react-query替换上下文

Pat*_*nny 5 typescript react-query

我读了一篇关于使用react-query作为状态管理器的文章,现在我尝试用react-query(版本4)替换我的应用程序中的上下文。

之前,我创建了一个上下文,其中useContext()存储了登录用户的用户帐户对象。我使用react-query来获取这些数据,然后useReducer()修改帐户对象。然而,我意识到这是一团糟,相关数据无论如何都在react-query中,所以我应该摆脱上下文和reducer,直接使用react-query(我在我所做的查询中生成用户帐户对象与反应查询)。

我在自定义挂钩中生成用户帐户对象:

function useUser(): UseQueryResult<User, Error> {
  const query = getInitialUserUrl;
  const platform = usePlatformContext();
  return useQuery<User, Error>(
    queryKeyUseUser,
    async () => {
      const data = await fetchAuth(query);
      if (didQueryReturnData(data)) {
        return new User(platform, data[0]);
      }
      return new User(platform);
    },
    {
      refetchOnReconnect: 'always',
      refetchInterval: false,
    },
  );
}

export default useUser;
Run Code Online (Sandbox Code Playgroud)

现在我有一个突变,我可以验证用户的电子邮件地址(使用上下文的旧方法):

const useMutationVerifyEmail = (): UseMutationResult<RpcResponseEmailVerify, Error, FormEmailVerifyInput> => {
  const { userObject } = useUserContext();

  return useMutation(
    (data: FormEmailVerifyInput) => verifyEmail(userObject.id, data.validation_code),
    },
  );
};
Run Code Online (Sandbox Code Playgroud)

我尝试将对上下文的调用替换为直接调用useUser()

const useMutationVerifyEmail = (): UseMutationResult<RpcResponseEmailVerify, Error, FormEmailVerifyInput> => {
  const { data: userObject } = useUser();

  return useMutation(
    (data: FormEmailVerifyInput) => verifyEmail(userObject.id, data.validation_code),
    },
  );
};
Run Code Online (Sandbox Code Playgroud)

然而,TypeScript 抱怨说,Object is possibly 'undefined'对于userObject. 我不知道为什么,因为据我了解,useUser()总是返回一个User对象。

如何更新我的自定义挂钩以返回User对象,以便我可以使用它而不是上下文?

更新

我可以将我的useUser()钩子包裹在另一个钩子中:

function useUserObject() {
  const { data: userObject } = useUser();
  if (userObject instanceof User) {
    return userObject;
  }
  throw new Error('Failed to get account info!');
}
Run Code Online (Sandbox Code Playgroud)

然后我可以const userObject = useUserObject()获取用户帐户对象...但这真的是最佳方法吗?我是否需要为自定义钩子创建一个自定义钩子只是为了像我一样使用我的用户对象useContext()?

TkD*_*odo 12

我写了你在问题中提到的文章。

不保证useQuery调用时返回数据,因为尚未获取数据获取可能失败。

现在,您可以在应用程序中保证这一点,因为您仅在已获取数据时调用挂钩 - 但 TypeScript 不知道这一点。

正如文章末尾所述,这是一种权衡。它给你的是每个钩子都是独立的。如果您useMutationVerifyEmail出于某种原因调用一个还没有用户对象的地方,它将去尝试获取它。

有多种方法可以“解决/解决”该问题/限制:

  1. 您可以忽略它并使用类型断言/ bang 运算符(!)
const useMutationVerifyEmail = (): UseMutationResult<RpcResponseEmailVerify, Error, FormEmailVerifyInput> => {
  const { data: userObject } = useUser();

  return useMutation(
    (data: FormEmailVerifyInput) => verifyEmail(userObject!.id, data.validation_code),
  );
};
Run Code Online (Sandbox Code Playgroud)

如果您将使用此钩子的组件移动到用户不可用的位置,这可能会在运行时出错,但如果您知道是这种情况,那就没问题了:)

  1. 您可以检查mutationFn内部作为不变量,如果没有用户则产生变异错误:
const useMutationVerifyEmail = (): UseMutationResult<RpcResponseEmailVerify, Error, FormEmailVerifyInput> => {
  const { data: userObject } = useUser();

  return useMutation(
    async (data: FormEmailVerifyInput) => {
      if (!userObject) {
        throw new Error("no user available")
      }
      return verifyEmail(userObject.id, data.validation_code)
    },
  );
};
Run Code Online (Sandbox Code Playgroud)
  1. 您可以将调用useUser移出自定义突变挂钩,然后将 userId 作为传递给突变的输入的一部分。
const useMutationVerifyEmail = (): UseMutationResult<RpcResponseEmailVerify, Error, FormEmailVerifyInput> => {
  return useMutation(
    ({ id, ...data} : FormEmailVerifyInput & { id: number }) => verifyEmail(id, data.validation_code),
  );
};
Run Code Online (Sandbox Code Playgroud)

然后,如果没有用户,组件可以调用useQuery(也许返回null)加载旋转器或错误,或者只要没有用户,您就可以禁用触发突变的按钮。

  1. 您可以后退一步并保留您的上下文,但用以下数据填充它useQuery
const UserProvider = ({ children }) => {
  const { data } = useUser()

  if (data.isLoading) return "loading ..."
  if (data.isError) return "error"

  return <UserContext.Provider value={data}>{children}</UserContext.Provider>
}
Run Code Online (Sandbox Code Playgroud)

我可能需要多写一些关于此模式的文章,因为它不是状态同步/复制。它也不使用反应上下文来管理状态。它所做的只是通过react-context 分发来自react-query 的数据。data当您通读它时,保证已定义useContext(),因此它消除了未定义的检查。

它还将始终为您提供react-query的“最新”数据,因为您仍然通过useQuery-在树的更上方进行订阅。由于更新,上下文消费者将收到更改通知value

现在也需要权衡,即:

  • 你不能再通过 进行部分订阅select,因为react-context没有选择器。
  • 如果您使用多个提供程序执行此操作,则可能会面临瀑布式获取的风险:获取用户会“阻止”应用程序的其余部分,因为children在获取用户之前它不会呈现。这就是为什么在子级中访问它时定义了 user (这是所需的),但它也会阻止子级中发生的所有其他获取。

我以前用类似EssentialDataProvider. 它会触发一堆 useQuery 调用,例如我们在应用程序中随处需要的用户和堆栈相关信息 - 因此在没有它的情况下渲染应用程序是没有意义的。然后我们通过上下文分发它,这样我们就不必在任何地方对其进行空检查。我们还在服务器上预取这些查询。

这是一个可供您使用的好工具,但正如一切一样,它有优点和缺点:)