React Query 完全指南,時下最熱辣的請求庫!

小夥伴們,是時候開始 React Query 之旅了。你還不知道這個庫嗎?完美,你來對地方了 😁

介紹

React Query 是什麼?React Query 是由 @TannerLinsley 創建的 npm 庫。它是一個針對 React 應用的狀態管理器,可以簡化許多任務,例如處理 HTTP 請求狀態、在客戶端保存數據以防止多次請求、使用 hooks 共享數據等等。

你將在本系列中發現更多關於它的內容,學習如何使用它,並欣賞其在 React 應用程序中的簡潔性。

useQuery

第一個核心概念是 useQuery。通過它,你可以以一種非常簡單的方式從源中檢索數據並處理此請求的所有狀態。

讓我們看一個例子:

import { useQuery } from '@tanstack/react-query';

const fetchTodos = async (): Promise<Todo[]={
  const response = await fetch('api/tasks');
  if (!response.ok) {
    throw new ResponseError('Failed to fetch todos', response);
  }
  return await response.json();
};

export const useTodos = ()UseTodos ={
  const {
    data: todos = [],
    isLoading,
    isFetching,
    error,
  } = useQuery(['todos'], fetchTodos, {
    refetchOnWindowFocus: false,
    retry: 2,
  });
  ...
};

在這個例子中,你可以看到 useQuery 的要點。

UseQuery 是一個 React hook,它需要三個參數:

  1. 查詢關鍵字

  2. 查詢函數

  3. 配置項

讓我們從第一個參數開始。查詢關鍵字是 React Query 用於識別你的查詢的關鍵字。通過該關鍵字,React Query 能夠存儲結果並在應用程序的不同部分中使用它。該關鍵字用於標識查詢,你還可以使用 React Query 客戶端通過代碼重置查詢或更改值。

查詢函數是用於從源(rest、GraphQL 等等)檢索數據的方法。它很簡單,一個返回某種數據的函數,可以是簡單函數或者大多數情況下是一個 promise。

然後是配置項,這些很簡單啦 :) 有許多可能的選項用於以不同的方式運行查詢(重試次數、何時刷新數據、如何緩存數據等等..)。

這個 hook 的結果有三個重要的屬性:

好的,你現在對 useQuery 的工作方式及其潛力有了一個概念,但是如果你更有興趣,可以觀看我的視頻瞭解更多信息。

好的,就這些!我很快會回到你呈現 React Query 的另一個功能。希望你喜歡這份內容。

突變

夥計們,是時候談論 React Query 中的第二個核心概念了,即突變。

這是什麼?
突變是用戶可以在你的應用程序中執行的操作,你可以將突變想象成更改或創建某些東西的操作。

爲了更好地在代碼中理解突變是什麼,讓我們從一個代碼片段開始

import { useMutation } from '@tanstack/react-query';

const postTodo = async (text: Todo['text']): Promise<Todo> ={
  const response = await fetch('api/tasks'{
    method: 'POST',
    headers: {
      'Content-Type''application/json',
    },
    body: JSON.stringify({ text }),
  });
  if (!response.ok) {
    throw new ResponseError('Failed to insert new todo', response);
  }
  return await response.json();
};

export const useAddTodo = ()UseAddTodo ={
  const { mutate: addTodo, isLoading, error } = useMutation(postTodo, {
    onSuccess: () ={
      // Success actions
    },
    onError: (error) ={
      // Error actions
    },
  });

  return {
    addTodo,
  };
};

正如你所看到的,突變是一個簡單的 hook,有兩個參數:

結果有三個主要的對象:

在 React 應用程序中使用突變,你可以處理所有那些操作來改變數據並簡化這些請求的狀態管理。

當你處理突變時,另一個重要的概念是 QueryClient。

使用 QueryClient,你可以使已經提供的查詢失效,並告訴 React Query 重新請求數據,因爲你可以確保在突變之後,那些數據還不是有效的。

爲了這樣做,你必須使用 useQueryClient 鉤子來檢索 queryClient,並使用 invalidateQueries 方法,你可以使 React Query 緩存無效,同時使指定的查詢或多個查詢失效。

以下是一個例子

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { QUERY_KEY } from '../../../../constants/queryKeys';

export const useAddTodo = ()UseAddTodo ={
  const client = useQueryClient();
  const { mutate: addTodo } = useMutation(postTodo, {
    onSuccess: () ={
      client.invalidateQueries([QUERY_KEY.todos]);
    },
  });
  ...
};

好的,我想你已經對如何使用 useMutation 和 useQueryClient 有了一個概念,但是如果你想深入瞭解它們,請別忘了看我的 Youtube 視頻。

React Query 提供的兩個 hooks:useIsFetching 和 useIsMutation。

這些 hooks 可用於瞭解應用程序中是否存在獲取請求或突變請求正在進行。

如果需要創建一個全局的加載器,在存在一個或多個請求進行時出現,它們就會很有用。

但是你如何使用它們呢?

我們先從 useIsFetching 開始。

import { useIsFetching } from '@tanstack/react-query';

export default function Loader() {
  const isFetching = useIsFetching();
  if (!isFetching) return null;

  return <>Fetching...</>
}

正如你所看到的,語法非常簡單。你可以從庫中導入該 hook 並在組件中使用。該 hook 僅返回一個布爾值,表示應用程序中是否存在一個或多個獲取請求。因此,你可以根據這些數據決定是否顯示加載器。Easy peasy!

現在是時候移動到 useIsMutation hook 了。這個 hook 類似於之前的那個,唯一不同的概念是這個 hook 處理的是突變請求。讓我們看一個例子!

import { useIsMutating } from '@tanstack/react-query';

export default function Loader() {
  const isMutating = useIsMutating();
  if (!isMutating) return null;

  return <>Mutating...</>
}

正如你所注意到的那樣,語法與之前的相同,唯一不同的是 hook 的名稱和其概念。

Dev tool

接下來,你將學習如何調試和檢查 React Query 應用程序中發生的一切。當你開始學習或使用一個工具時,檢查它周圍的工具以瞭解開發者體驗是很正常的,這樣你就可以決定是否繼續使用它。React Query 團隊知道這一點,並決定構建一個工具來幫助那些想要使用 React Query 進行工作的開發者。

這個工具叫做react-query-devtools,你只需要通過一個簡單的步驟安裝它。

打開你的終端並輸入

$ npm i @tanstack/react-query-devtools

現在,在你的項目中,你可以使用它並得到所有需要調試你的應用程序所需的信息。

這個工具很容易使用。在你的應用程序中,你必須將它導入並在你渲染ReactQueryProvider的地方渲染它。

import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import React from "react";
import { queryClient } from './react-query/client';
import Router from './Router';

function App() {
  return (
    <React.StrictMode>
      <QueryClientProvider client={queryClient}>
        ...
        <ReactQueryDevtools />
      </QueryClientProvider>
    </React.StrictMode>
  );
}
export default App;

Easy peasy, no? 😎

使用ReactQueryDevtools,你不需要關注環境是否渲染該組件,因爲它默認提供了它。它僅在條件process.env.NODE_ENV === 'development'爲 true 時才渲染該組件。

如果需要,你可以自定義該組件或強制在生產模式下渲染它。要了解更多相關主題,請查閱文檔。

在你的應用程序中使用該組件的好處在於,它允許在運行時查看 ReactQuery 中發生的情況。你可以檢查狀態中保存的數據,不同的查詢有多少應用程序部分使用等等。你也可以重置狀態或刪除部分狀態以重新獲取數據。

沒錯,它提供了許多很好的功能來調試和檢查你的 React Query 應用程序,並且它是每個使用 React Query 的開發者的好工具。在這裏,你可以找到一個 ReactQueryDevtool 的示例。

權限

每個應用程序都應該處理認證流程;在這篇文章中,你將學習如何使用 React Query 在你的 React 應用程序中構建認證流程。

註冊

構建認證流程的第一步是註冊操作。通過本系列你已經學習到,你應該構建一個 mutation 來執行此操作。一種可能的解決方法如下:

async function signUp(email: string, password: string): Promise<User> {
  const response = await fetch('/api/auth/signup'{
    method: 'POST',
    headers: {
      'Content-Type''application/json'
    },
    body: JSON.stringify({ email, password })
  })
  if (!response.ok)
    throw new ResponseError('Failed on sign up request', response);

  return await response.json();
}

type IUseSignUp = UseMutateFunction<User, unknown, {
  email: string;
  password: string;
}, unknown>

export function useSignUp(): IUseSignUp {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const { enqueueSnackbar } = useSnackbar();

  const { mutate: signUpMutation } = useMutation<User, unknown, { email: string, password: string }, unknown>(
    ({
      email,
      password
    }) => signUp(email, password){
    onSuccess: (data) ={
      // TODO: save the user in the state
      navigate('/');
    },
    onError: (error) ={
      enqueueSnackbar('Ops.. Error on sign up. Try again!'{
        variant: 'error'
      });
    }
  });

  return signUpMutation;
}

通過創建這樣的 mutation,你可以非常簡單和清晰地構建一個註冊操作。

現在使用 useSignUp hook,你可以獲取 mutation 並調用 signUp 請求在你的系統中創建新用戶。正如你可以看到的,代碼非常簡單,signUp 方法調用 API 來發布新用戶的數據並返回保存在數據庫中的用戶數據。然後使用 useMutation hook,可以構建處理 signUp 操作的 mutation。如果一切正常,onSuccess hook 調用導航到主頁;否則,onError hook 顯示一個錯誤的提示。

在代碼中,有一個 TODO 表示缺失的內容;我們將在此後的文章中回到這行代碼。

登錄

如果你正在建立一個身份驗證流程,那麼 SignIn 是構建的第二個步驟。在這種情況下,SignIn 與 SignUp 非常相似;唯一變化的是終點和 Hook 的範圍。

所以代碼可以是這樣的:

async function signIn(email: string, password: string): Promise<User> {
  const response = await fetch('/api/auth/signin'{
    method: 'POST',
    headers: {
      'Content-Type''application/json'
    },
    body: JSON.stringify({ email, password })
  })
  if (!response.ok)
    throw new ResponseError('Failed on sign in request', response);

  return await response.json();
}

type IUseSignIn = UseMutateFunction<User, unknown, {
  email: string;
  password: string;
}, unknown>

export function useSignIn(): IUseSignIn {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const { enqueueSnackbar } = useSnackbar();

  const { mutate: signInMutation } = useMutation<User, unknown, { email: string, password: string }, unknown>(
    ({
      email,
      password
    }) => signIn(email, password){
    onSuccess: (data) ={
      // TODO: save the user in the state
      navigate('/');
    },
    onError: (error) ={
      enqueueSnackbar('Ops.. Error on sign in. Try again!'{
        variant: 'error'
      });
    }
  });

  return signInMutation;
}

用戶

身份驗證流程的核心部分是將用戶保存在狀態中。爲了做到這一點,在這種情況下,最好的方法是創建一個稱爲 useUser 的新 hook,它是用戶數據的所有者。

useUser hook 必須具有用戶數據,並且它必須將用戶數據保存在本地存儲中,並在以後刷新頁面或返回時檢索它們。

先從處理本地存儲的代碼開始,通常使用具有特定目標的小功能創建此代碼,例如:

import { User } from './useUser';

const USER_LOCAL_STORAGE_KEY = 'TODO_LIST-USER';

export function saveUser(user: User): void {
  localStorage.setItem(USER_LOCAL_STORAGE_KEY, JSON.stringify(user));
}

export function getUser(): User | undefined {
  const user = localStorage.getItem(USER_LOCAL_STORAGE_KEY);
  return user ? JSON.parse(user) : undefined;
}

export function removeUser(): void {
  localStorage.removeItem(USER_LOCAL_STORAGE_KEY);
}

以這種方式,您可以創建一個處理用戶的所有本地存儲函數的小模塊。

現在是時候看看如何構建 useUser hook 了。

先從以下代碼開始:

async function getUser(user: User | null | undefined): Promise<User | null> {
  if (!user) return null;
  const response = await fetch(`/api/users/${user.user.id}`{
    headers: {
      Authorization: `Bearer ${user.accessToken}`
    }
  })
  if (!response.ok)
    throw new ResponseError('Failed on get user request', response);

  return await response.json();
}

export interface User {
  accessToken: string;
  user: {
    email: string;
    id: number;
  }
}

interface IUseUser {
  user: User | null;
}

export function useUser(): IUseUser {
  const { data: user } = useQuery<User | null>(
    [QUERY_KEY.user],
    async (): Promise<User | null> => getUser(user),
    {
      refetchOnMount: false,
      refetchOnWindowFocus: false,
      refetchOnReconnect: false,
      initialData: userLocalStorage.getUser,
      onError: () ={
        userLocalStorage.removeUser();
      }
    });

  useEffect(() ={
    if (!user) userLocalStorage.removeUser();
    else userLocalStorage.saveUser(user);
  }[user]);

  return {
    user: user ?? null,
  }
}

getUser 函數很簡單,它提供獲取用戶信息的 HTTP 請求;如果用戶爲空,則返回 null,否則調用 HTTP 終點。

useQuery hook 與之前看到的其他 hook 類似,但有兩個新配置需要了解。

現在您具備了身份驗證流程的所有塊,但是現在是將 useSignUp 和 useSignIn 與 useUser hook 鏈接起來的時候了。

使用 QueryClient,您可以使用 setQueryData 函數設置特定查詢的數據。

因此,以以下方式更改以前的 TODOs 註釋:

export function useSignUp(): IUseSignUp {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const { enqueueSnackbar } = useSnackbar();

  const { mutate: signUpMutation } = useMutation<User, unknown, { email: string, password: string }, unknown>(
    ({
      email,
      password
    }) => signUp(email, password){
    onSuccess: (data) ={
      queryClient.setQueryData([QUERY_KEY.user], data);
      navigate('/');
    },
    onError: (error) ={
      enqueueSnackbar('Ops.. Error on sign up. Try again!'{
        variant: 'error'
      });
    }
  });

  return signUpMutation;
}


export function useSignIn(): IUseSignIn {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const { enqueueSnackbar } = useSnackbar();

  const { mutate: signInMutation } = useMutation<User, unknown, { email: string, password: string }, unknown>(
    ({
      email,
      password
    }) => signIn(email, password){
    onSuccess: (data) ={
      queryClient.setQueryData([QUERY_KEY.user], data);
      navigate('/');
    },
    onError: (error) ={
      enqueueSnackbar('Ops.. Error on sign in. Try again!'{
        variant: 'error'
      });
    }
  });

  return signInMutation;
}

只需兩行簡單的代碼,您就可以將用戶設置到 useUser 狀態中,因爲設置查詢數據的鍵與 useUser 相同。

然後,使用 useUser hook 中的 useEffect,可以在用戶更改時刪除或設置用戶數據到本地存儲中:

export function useUser(): IUseUser {
  const { data: user } = useQuery<User | null>(
    [QUERY_KEY.user],
    async (): Promise<User | null> => getUser(user),
    {
      refetchOnMount: false,
      refetchOnWindowFocus: false,
      refetchOnReconnect: false,
      initialData: userLocalStorage.getUser,
      onError: () ={
        userLocalStorage.removeUser();
      }
    });

  useEffect(() ={
    if (!user) userLocalStorage.removeUser();
    else userLocalStorage.saveUser(user);
  }[user]);

  return {
    user: user ?? null,
  }
}

要完成身份驗證流程,唯一缺少的是註銷。

可以使用一個名爲 useSignOut 的自定義 hook 來構建它;它的實現很簡單,如下所示:

import { useQueryClient } from '@tanstack/react-query';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { QUERY_KEY } from '../constants/queryKeys';

type IUseSignOut = () => void

export function useSignOut(): IUseSignOut {
  const queryClient = useQueryClient();
  const navigate = useNavigate();

  const onSignOut = useCallback(() ={
    queryClient.setQueryData([QUERY_KEY.user], null);
    navigate('/auth/sign-in');
  }[navigate, queryClient])

  return onSignOut;
}

正如您可以注意到的那樣,hook 返回一個簡單的函數,該函數清除用戶狀態中的值並導航到登錄頁面。

好的,完美。現在您已具備使用 React Query 構建身份驗證流程的所有知識!

原文:https://dev.to/this-is-learning/react-query-usequery-36i

翻譯 / 潤色:ssh

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/E55QGLxBQhIMn6IZAE8mNQ