react-query系列一——基礎(chǔ)及useQuery使用

為什么要寫這篇文章

  1. 公司業(yè)務(wù)使用react-query,但目前只停留在“能用”的階段,離真正會(huì)用并且靈活使用API還有很長(zhǎng)距離

react-query介紹

react-query是React數(shù)據(jù)獲取(date-fetch)庫,在使用Hooks寫組件時(shí),發(fā)起異步請(qǐng)求時(shí),不僅需要管理請(qǐng)求狀態(tài),而且還需要處理異步數(shù)據(jù),為此要多寫幾個(gè)useState/useEffect來控制。

而react-query也是一個(gè)Hooks庫,使用很少的代碼完成對(duì)服務(wù)端的狀態(tài)管理,而且大多數(shù)情況下使用查詢useQuery和修改useMutation就可以了

我們知道redux可以輕松的管理客戶端狀態(tài),但并不適合處理異步和服務(wù)端狀態(tài),服務(wù)端狀態(tài)有以下比較復(fù)雜的點(diǎn):

  • 緩存...(數(shù)據(jù)未變化時(shí)不去請(qǐng)求)
  • 知道數(shù)據(jù)何時(shí)“過時(shí)”
  • 在后臺(tái)更新“過時(shí)”的數(shù)據(jù)
  • 分頁、延遲加載等性能優(yōu)化
  • 結(jié)構(gòu)化共享并存儲(chǔ)查詢結(jié)果

而react-query正是為此而生,可以方便的管理服務(wù)端的狀態(tài)

安裝

# use npm
npm i react-query
# use yarn
yarn add react-query

使用

  1. 在App.tsx中創(chuàng)建全局實(shí)例client,并通過QueryClientProviderclient傳遞下去,用于管理所有請(qǐng)求

    import './App.css'
     import {
       QueryClient,
       QueryClientProvider,
     } from 'react-query'
     import { ReactQueryDevtools } from 'react-query/devtools';
     import Demo1 from './components/Demo1'
    
     // 創(chuàng)建一個(gè) client
     const queryClient = new QueryClient()
     function App() {
       return (
         // 提供client
         <QueryClientProvider client={queryClient}>
           {/* 添加devtools */}
           {process.env.NODE_ENV === 'development' ? (
             <ReactQueryDevtools initialIsOpen={false} position='bottom-right' />
           ) : (
             ''
           )}
           <Demo1 />
         </QueryClientProvider>
       )
     }
    
     export default App
    
  2. 在組件中使用useQuery和useMutation,通過useQueryClient獲取到全局QueryClient實(shí)例,調(diào)用api管理react-query的請(qǐng)求,如queryClient.invalidateQueries('posts')

     import axios from 'axios';
     import { useMutation, useQuery, useQueryClient } from 'react-query';
    
     type dataType = {
       id: string
       title: string
     }
     const Demo1 = () => {
       // 訪問App QueryClientProvider提供的client 
       const queryClient = useQueryClient();
       const query = useQuery('posts', () => axios.get('https://jsonplaceholder.typicode.com/posts'))
       console.log(query);
       const { data, isLoading, isError } = query;
    
       const { mutate } = useMutation(() => axios.delete('https://jsonplaceholder.typicode.com/posts/1'), {
         onSuccess: () => {
           // 錯(cuò)誤處理和刷新
           queryClient.invalidateQueries('posts')
         },
       })
       
       if (isError) {
         return <div>error</div>;
       }
       if (isLoading) {
         return <div>loading</div>;
       }
       
       return (
         <>
           <button
             onClick={() => {
               mutate()
             }}
           >
             Delete
           </button>
         <ul>
           {(data?.data as unknown as dataType[])?.map(d => <li key={d.id}>{d.title}</li>)}
         </ul>
         </>
       )
     }
    
     export default Demo1
    
    useQuery-query.jpg
  • useQuery接收一個(gè)唯一鍵和一個(gè)返回Promise的函數(shù)以及config [queryKey, queryFn, config],如posts在內(nèi)部用于在整個(gè)程序中重新獲取數(shù)據(jù)、緩存和共享查詢等
  • 通過打印query會(huì)看到,React-Query將所有的請(qǐng)求中間狀態(tài)進(jìn)行封裝
  • isFetching 或者 status === 'fetching' 類似于isLoading,不過每次請(qǐng)求時(shí)都為true,所以使用isFetching作為loading態(tài)更好
  • isLoading 或者 status === 'loading' 查詢沒有數(shù)據(jù),正在獲取結(jié)果中,只有“硬加載”時(shí)才為true,只要請(qǐng)求在cacheTime設(shè)定時(shí)間內(nèi),再次請(qǐng)求就會(huì)直接使用cache,即“isLoaindg = isFetching + no cached data”
  • isError 或者 status === 'error' 查詢遇到一個(gè)錯(cuò)誤,此時(shí)可以通過 error 獲取到錯(cuò)誤
  • isSuccess 或者 status === 'success' 查詢成功,并且數(shù)據(jù)可用,通過 data 獲取數(shù)據(jù)
  • isIdle 或者 status === 'idle' 查詢處于禁用狀態(tài)

示例:

import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from 'react-query'
import { getTodos, postTodo } from '../my-api'

// 創(chuàng)建一個(gè) client
const queryClient = new QueryClient()

function App() {
  return (
    // 提供 client 至 App
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  )
}

function Todos() {
  // 訪問 client
  const queryClient = useQueryClient()

  // 查詢
  const query = useQuery('todos', getTodos)

  // 修改
  const mutation = useMutation(postTodo, {
    onSuccess: () => {
      // 錯(cuò)誤處理和刷新
      queryClient.invalidateQueries('todos')
    },
  })

  return (
    <div>
      <ul>
        {query.data.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>

      <button
        onClick={() => {
          mutation.mutate({
            id: Date.now(),
            title: 'Do Laundry',
          })
        }}
      >
        Add Todo
      </button>
    </div>
  )
}

render(<App />, document.getElementById('root'))

重要知識(shí)點(diǎn)

  • refetchOnWindowFocus

    const queryClient = new QueryClient({
      defaultOptions: {
        queries: {
          refetchOnWindowFocus: false,
        },
      },
    })
    

    refetchOnWindowFocus默認(rèn)為true,用戶短暫離開再返回應(yīng)用頁時(shí),數(shù)據(jù)就會(huì)被標(biāo)記為過時(shí),這時(shí)react-query會(huì)在后臺(tái)自動(dòng)請(qǐng)求新的數(shù)據(jù),通過設(shè)置refetchOnWindowFocus為false禁用

  • query-keys

    • 字符串作為query-keys時(shí),會(huì)在內(nèi)部轉(zhuǎn)換為數(shù)組

      useQuery('posts', ...) // queryKey === ['posts']
      
    • 數(shù)組作為queryKey,查詢功能依賴于變量,類似于useEffect,則將其包含在查詢鍵值中,盡量使用數(shù)組

      useQuery(["posts", postId], ...);
      
  • query-functions

    任何一個(gè)返回Promise的函數(shù)

    useQuery(["todos"], fetchAllTodos);
    useQuery(["todos", todoId], () => fetchTodoById(todoId));
    useQuery(["todos", todoId], async () => {
      const data = await fetchTodoById(todoId);
      return data;
    });
    // 通過解構(gòu)queryKey可以拿到傳遞的query-keys
    useQuery(["todos", todoId], ({ queryKey }) => fetchTodoById(queryKey[1]), {
      enabled: false,
      retry: 3,
      select: data => {},
      onSuccess: data => {},
      onError: error => {}
      ...
    });
    

    useQuery的config配置有很多,API

  • 并行查詢parallel-queries

    同時(shí)執(zhí)行的查詢

    function App () {
      // 下面的查詢將自動(dòng)地并行執(zhí)行
      const usersQuery = useQuery('users', fetchUsers)
      const teamsQuery = useQuery('teams', fetchTeams)
      const projectsQuery = useQuery('projects', fetchProjects)
      ...
    }
    

    React-query提供useQueries動(dòng)態(tài)并行查詢

    function App({ users }) {
      const userQueries = useQueries(
        users.map((user) => {
          return {
            queryKey: ["user", user.id],
            queryFn: () => fetchUserById(user.id),
          };
        }),
      );
    }
    
  • 有依賴的查詢 enabled

    具有相依性的query:當(dāng)有多個(gè)query 設(shè)定相依性后,前一個(gè)query 必須成功執(zhí)行并取得資料,下一個(gè)query 才會(huì)接續(xù)執(zhí)行。

    開啟/關(guān)閉查詢:假設(shè)我們有一個(gè)定時(shí)查詢,通過refetchInterval來實(shí)現(xiàn),但是當(dāng)一個(gè)彈窗打開的時(shí)候我們可以暫停這個(gè)查詢,避免彈窗后面的內(nèi)容發(fā)生變更。
    demo

  • 緩存

    • useQuery和useInfiniteQuery生成的查詢實(shí)例會(huì)立即將緩存數(shù)據(jù)視為過時(shí)(slate)的


      useQuery-slate.jpg
  • staleTime(不新鮮時(shí)間) 默認(rèn)0,可全局或單獨(dú)配置,在此段時(shí)間內(nèi)再次遇到相同key的請(qǐng)求,不會(huì)再去獲取數(shù)據(jù),直接從緩存中獲取,isFetching也為false,如果設(shè)置為Infinity,則當(dāng)前查詢的數(shù)據(jù)只會(huì)獲取一次,在整個(gè)網(wǎng)頁的生命周期內(nèi)緩存
useQuery('posts', axios => ('https://jsonplaceholder.typicode.com/posts'), {
  select: ({ data }) => {
    return data.data;
  },
  cacheTime: Infinity,
  staleTime: Infinity,
});

通過devTools可以看到此時(shí)數(shù)據(jù)是fresh狀態(tài)


useQuery-alsteTime.jpg

這時(shí)候頁面上的所有相同query-keys的請(qǐng)求都會(huì)被緩存起來,想要重新請(qǐng)求就需要清空緩存

queryClient.invalidateQueries('todos');
  • cacheTime(緩存時(shí)間) 數(shù)據(jù)在內(nèi)存中的緩存時(shí)間,默認(rèn)5分鐘,在不設(shè)置slateTime時(shí),如果緩存期內(nèi)遇到相同key的請(qǐng)求,雖然會(huì)直接使用緩存數(shù)據(jù)呈現(xiàn)UI,但還是會(huì)獲取新數(shù)據(jù),待獲取完畢后切換為新數(shù)據(jù),isFetching為true;如果某個(gè)queryKey未被使用時(shí),這個(gè)query就會(huì)進(jìn)入inactive狀態(tài),如果在cacheTime設(shè)定的時(shí)間內(nèi)未被使用的話,這個(gè)query及其data就會(huì)被清除

    useQuery-inactive.jpg

可以看到此時(shí)["post", 3]["post", 2]是inactive狀態(tài),過設(shè)定的cacheTime后會(huì)被清除

參考資料

github倉庫

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容