Site icon May's Notes

React Query 基礎使用指南

React-Query-logo

React Query 是一個 data-fetching 庫,幫助你更有效的管理 React 中的非同步狀態。

Redux-Toolkit Query vs React Query

使用過 RTK Query 和 React Query 後,我個人是更喜歡 React query,畢竟好上手、開發效率高,而且需要的功能基本都已經提供不需要自己再額外去處理,所以我認為學習 React Query 是划算的”投資”。

下面簡單對比一下 RTK Query 和 React Query 的優缺點,可以根據自己的專案需求來選擇要使用哪一個。

Redux-Toolkit Query

優點:

缺點:

React Query

優點:

缺點:

小結

總結一下,React Query 適合中小型需要快速開發和管理較為簡單的狀態的專案。而 Redux Toolkit Query 適合需要管理更複雜的狀態或已經使用 Redux 的專案。

以上這些只是比較基本的比較,如果需要了解更詳細的比較,可以看官方寫的 Comparison

安裝 React Query

npm i react-query
# or
yarn add react-query
# or
pnpm add @tanstack/react-query

與瀏覽器的相容性:

建議可以使用 eslint 插件來協助開發:

npm i -D @tanstack/eslint-plugin-query
# or
pnpm add -D @tanstack/eslint-plugin-query
# o
yarn add -D @tanstack/eslint-plugin-query

相關配置請看:ESLint plugin query

創建 Client

首先需要使用 QueryClient 創建一個 client,並在 App 組件外包裹 <QueryClientProvider>,將 client 提供給其他組件做使用:

import React from 'react'
import ReactDOM from 'react-dom/client'
import { QueryClientProvider, QueryClient, QueryCache } from '@tanstack/react-query'
import App from './App.jsx'

const queryClient = new QueryClient()

ReactDOM.createRoot(document.getElementById('root')).render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>
)

使用 Hook 發送 CRUD 請求

import React from 'react'
import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query'
import { getStudents, updateStudent, postStudent, deleteStudent } from './api'

export const App = () => {
  const students = useQuery({
    queryKey: ['students'],
    queryFn: getStudents
  })
  const addStudent = useMutation({
    mutationFn: postStudent,
  })
  const editStudent = useMutation({
    mutationFn: updateStudent,
  })
  const delStudent = useMutation({
    mutationFn: deleteStudent,
  })

  ...
}

useQuery

useQuery 的參數是一個物件,常見的屬性有:

const { data: students, isSuccess, isPending } = useQuery({
  queryKey: ['students'],
  queryFn: getStudents,
})

回傳的主要內容包括:

data 是一個物件,其中又會包含請求的相關內容:

所以如果要獲取 Response,應該要取兩層 data,這是一開始比較容易出錯的地方。

queryKey

React Query 需要藉由 queryKey 來區分多個不同的查詢請求,以及對請求的結果進行快取。

當 queryKey 發生變化時,React Query 將會自動重新發送請求。

假設我需要使用名稱、性別來對數據進行篩選,就可以將篩選的內容作為元素放進 queryKey:

const [filters, setFilters] = useState({ name: undefined, gender: undefined })

const { data: students, isSuccess, isPending } = useQuery({
    queryKey: ['students', filters],
    queryFn: ({ signal }) => getStudents(filters),
  })

當 name 或者 gender 改變時就會自動重新發送請求獲取相應的數據。

enable

如果要禁止 query 自動執行,可以設置 enabled: false

舉個簡單的例子,如果沒有 id 的時候就不請求 getStudentById,可以寫成:

const { data: student, isSuccess } = useQuery({
    queryKey: ['students', id],
    queryFn: () => getStudentById(id),
    enabled: !!id,
  })

placeholderData & initialData

通常我們會希望在沒有拿到數據之前給定一個初始值,比如說空陣列。這時候就可以傳入 placeholderData 或者 initialData

不過區別在於,initialData 會被存到快取中,而 paceholderData 不會,通常來說 placeholderData 會比較符合我們常見的需求。

const { data: students, isSuccess, isPending } = useQuery({
  queryKey: ['students', filters],
  queryFn: ({ signal }) => getStudents(filters),
  placeholderData: [],
})

useMutation

useMutation 的參數是一個物件,常見的屬性有:

import React, { useState } from 'react'
import { useQueryClient, useQuery, useMutation } from '@tanstack/react-query'
import { getStudents, updateStudent, postStudent, deleteStudent } from './services/api'

const App = () => {
  const queryClient = useQueryClient()

  const addStudent = useMutation({
    mutationFn: postStudent,
    onSuccess: () => {

    },
  })
  const editStudent = useMutation({
    mutationFn: updateStudent,
    onSuccess: (data, variables, context) => {

    },
  })
  const delStudent = useMutation({
    mutationFn: deleteStudent,
    onSuccess: () => {

    },
  })

  ...
}

mutation 的調用方式有兩種:

傳入 mutate 的參數會帶入到 useMutation 的 mutateFn 中。

/* mutate */
addStudent.mutate(
  data,
  {
    onSuccess: () => {
        console.log('Success')
    },
    onError: (err) => {
        console.log(err)
    }
  }
)

/* mutateAsync */
try {
    await addStudent.mutateAsync(data)
    console.log('Success')
} catch (err) {
    console.log(err)
}

// or
addStudent.mutateAsync(data)
    .then(() => console.log('Success'))
    .catch(() => console.log(err))

要注意的是 useMutation 和 mutate 都可以傳入 callback,執行順序上 useMutation 的 callback 會先於 mutate 的 callback。

invalidateQueries

一般來說在新增、編輯、刪除資料後,會需要重新查詢資料,使用 React query 的話不需要自行再調用函數重新發送請求,只需要調用 queryClient.invalidateQueries()

invalidateQueries 的目的是使 query 無效,當 query 一無效就會重新執行查詢。

invalidateQueries 的參數是一個物件。如果需要使特定變數的查詢失效,可以傳入 queryKey,和 useQuery 中的 queryKey 是一樣的。

const { data: students, isSuccess, isPending } = useQuery({
    queryKey: ['students', filters],
    queryFn: ({ signal }) => getStudents(filters, signal),
    placeholderData: [],
  })

const addStudent = useMutation({
    mutationFn: postStudent,
    onSuccess: () => {
      return queryClient.invalidateQueries({ queryKey: ['students'] })
    },
  })
  const editStudent = useMutation({
    mutationFn: updateStudent,
    onSuccess: (data, variables, context) => {
      return queryClient.invalidateQueries({ queryKey: ['students', variables.id] })
    },
  })

如果希望 invalidateQueries 完成後再結束 mutation,記得要 return

Exit mobile version