React-Query 讓你的狀態管理更優雅

下面就是一個簡單的從服務端獲取數據的案例javascript

function App() {

  const [data, updateData] = useState(null);
  const [isError, setError] = useState(false);
  const [isLoading, setLoading] = useState(false);
  
  useEffect(async () => {
    setError(false);
    setLoading(true);
    try {
      const data = await axios.get('/api/user');
      updateData(data);
    } catch(e) {
      setError(true);
    }
    setLoading(false);
  }, [])

}
複製代碼

在使用 React Hooks 編寫組件時,咱們常須要手動維護來自服務器的處理狀態。在平常開發中引發一些麻煩。 處理異步數據時,咱們須要考慮不少事情,例如更新,緩存或從新獲取。 使用 React-Query 可以更高效的幫你管理服務端的狀態。java

React-Query 是什麼

這是一個適用於 React Hooks 的請求庫。 這個庫將幫助你獲取、同步、更新和緩存你的遠程數據, 提供兩個簡單的 hooks,就能完成增刪改查等操做React-Query 使用聲明式管理服務端狀態,能夠使用零配置開箱即用地處理緩存,後臺更新和陳舊數據。無需繁瑣的配置,編寫useReduce,以及維護全局狀態,只要知道如何使用 Promise 或 async / await ,傳遞一個可解析數據(或引起錯誤)的函數,剩下的交給 React-Query 就行了,使用更少的代碼得到更高的效率.react

開始使用 React-Query

安裝 React-Queryios

 $ npm i react-query
 # or
 $ yarn add react-query
複製代碼

下面咱們用 React-Query改寫git

import { useQuery } from 'react-query'
 
 function App() {

   const {data, isLoading, isError} = useQuery('userData', () => axios.get('/api/user'));
   
   if (isLoading) {
     return <div>loading</div>;
   }
   
   return (
     <ul> {data.map(user => <li key={user.id}>{user.name}</li>)} </ul>
   )
 }

複製代碼

例子中 "userData" 字符串就是這個 query 獨一無二的key。 能夠看到,React-Query封裝了完整的請求中間狀態(isLoading、isError...)。 不只如此,React-Query還爲咱們作了以下工做:github

  1. 多個組件請求同一個query時只發出一個請求
  2. 緩存數據失效/更新策略(判斷緩存合適失效,失效後自動請求數據)
  3. 對失效數據垃圾清理

React-Query 中的三個重要知識點

經常使用參數配置

  • staleTime 從新獲取數據的時間間隔 默認0
  • cacheTime 數據緩存時間 默認 1000 60 5 5分鐘
  • retry 失敗重試次數 默認 3次
  • refetchOnWindowFocus 窗口從新得到焦點時從新獲取數據 默認 false
  • refetchOnReconnect 網絡從新連接
  • refetchOnMount 實例從新掛載
  • enabled 若是爲「false」的化,「useQuery」不會觸發,須要使用其返回的「refetch」來觸發操做

如何全局配置呢?以下:shell

import { ReactQueryConfigProvider, ReactQueryProviderConfig } from 'react-query';

const queryConfig: ReactQueryProviderConfig = {
  /** * refetchOnWindowFocus 窗口得到焦點時從新獲取數據 * staleTime 過多久從新獲取服務端數據 * cacheTime 數據緩存時間 默認是 5 * 60 * 1000 5分鐘 */
  queries: { 
    refetchOnWindowFocus: true,
    staleTime: 5 * 60 * 1000, 
    retry: 0
  },
};

ReactDOM.render(
    <ReactQueryConfigProvider config={queryConfig}> <App /> </ReactQueryConfigProvider>
    document.getElementById('root')
  );
也能夠單獨配置,以下:

function Todos() {
   // 第三個參數便可傳參了
   // "enabled"參數爲false的化,不會自動發起請求,而是須要調用「refetch」來觸發
   const {
     isIdle,
     isLoading,
     isError,
     data,
     error,
     refetch,
     isFetching,
   } = useQuery('todos', fetchTodoList, {
     enabled: false,
   })
 
   return (
     <> <button onClick={() => refetch()}>Fetch Todos</button> {isIdle ? ( 'Not ready...' ) : isLoading ? ( <span>Loading...</span> ) : isError ? ( <span>Error: {error.message}</span> ) : ( <> <ul> {data.map(todo => ( <li key={todo.id}>{todo.title}</li> ))} </ul> <div>{isFetching ? 'Fetching...' : null}</div> </>
       )}
     </>
   )
 }
複製代碼

數據查詢與操做

useQuery(查)查詢數據 (Get)npm

基本使用方法axios

function Todos() {
   // useQuery的第一個參數,做爲useQuery查詢的惟一標識,該值惟一
   // 能夠是string、array、object
   // string -> useQuery('todos', ...) queryKey === ['todos']
   // array -> useQuery(['todo', 5], ...) queryKey === ['todo', 5]
   // object -> useQuery(['todo', 5, { preview: true }], ...) queryKey === ['todo', 5, { preview: true }]
   const { isLoading, isError, data, error } = useQuery('todos', fetchTodoList)

   if (isLoading) {
     return <span>Loading...</span>
   }

   if (isError) {
     return <span>Error: {error.message}</span>
   }

   // also status === 'success', but "else" logic works, too
   return (
     <ul> {data.map(todo => ( <li key={todo.id}>{todo.title}</li> ))} </ul>
   )
 }
複製代碼

傳遞參數segmentfault

function Todos({ completed }) {
    // useQuery(['todo', { status: 1, page: 1 }], ...) queryKey === ['todo', { status: 1, page: 1 }]
    // 傳遞參數給「fetchTodoList」使用
   const queryInfo = useQuery(['todos', { status: 1, page: 1 }], fetchTodoList)
 }

 // 函數參數
 // key -> 「todos」
 // status -> 1 page -> 1
 function fetchTodoList(key, { status, page }) {
   return new Promise()
   // ...
 }
複製代碼

該庫還實現了經常使用的查詢操做:

useMutation(增、改、刪)操做數據 (Post,Delete,Patch,Put

// 當「mutate()」被調用時,執行「pingMutation」
const PingPong = () => {
   const [mutate, { status, data, error }] = useMutation(pingMutation)

   const onPing = async () => {
     try {
       const data = await mutate()
       console.log(data)
     } catch {
     }
   }
   return <button onClick={onPing}>Ping</button>
 }
複製代碼

傳遞參數

// "mutate({title})"就會將參數「title」傳遞給「createTodo」函數了
const createTodo = ({ title }) => {
  console.log("title ", title)
}

const CreateTodo = () => {
const [title, setTitle] = useState('')
const [mutate] = useMutation(createTodo)

const onCreateTodo = async e => {
    e.preventDefault()

    try {
    await mutate({ title })
    // Todo was successfully created
    } catch (error) {
    // Uh oh, something went wrong
    }
}

return (
    <form onSubmit={onCreateTodo}> <input type="text" value={title} onChange={e => setTitle(e.target.value)} /> <br /> <button type="submit">Create Todo</button> </form>
)
}
複製代碼

清除緩存

每當咱們編輯完一篇文章,返回列表頁面,若是不清除緩存,那麼數據仍是緩存的數據,因此須要清除緩存,使得「userQuery」失效,回到列表頁的時候從新拉取最新數據

參考代碼以下

import { useMutation, useQueryCache } from 'react-query'

const queryCache = useQueryCache()

const [mutate] = useMutation(addTodo, {
    onSuccess: () => {
        // invalidateQueries 的匹配規則
        // eg:
        // queryCache.invalidateQueries('todos') 那麼以下兩個`query key`都會被匹配到,匹配到的緩存都會失效
        // const todoListQuery = useQuery('todos', fetchTodoList)
        // const todoListQuery = useQuery(['todos', { page: 1 }], fetchTodoList)
        queryCache.invalidateQueries('todos')
        queryCache.invalidateQueries('reminders')
    },
})
複製代碼

Devtools 配套開發工具

導入開發工具

import { ReactQueryDevtools } from 'react-query/devtools'
複製代碼

默認狀況下,當process.env.NODE ENV === 'production' 時開啓 Devtools ,沒必要擔憂構建時須要排除他們

浮動模式下開啓,會將devtools做爲固定的浮動元素安裝在開發的應用程序中,並在屏幕一角提供一個切換按鈕以顯示和隱藏devtools。

儘量將如下代碼放在您的React應用程序中。 它離頁面根目錄越近,效果越好!

import { ReactQueryDevtools } from 'react-query/devtools'
 
 function App() {
   return (
     <QueryClientProvider client={queryClient}> {/* The rest of your application */} <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider>
   )
 }

複製代碼

React-Query 周邊生態

React-Query 周邊生態知足平常開發需求,支持 TypeScript ,GraphQL 。並可在 React Native 中使用,同時也兼容服務端渲染方案 SSR 支持 Next.js 的使用

其餘方案對比

  1. react-query 對於 mutation 可以使用 hooks,支持更多選項 (如 keepPreviousData),功能更多,更適合 API 複雜的項目
  2. swr 相對輕量,可隨處使用,不須要在父組件設置 Provider 。更加輕便

官網相關方案對比表格

React Query SWR (Website) Apollo Client (Website) RTK-Query (Website)
Github Repo / Stars 18K 16K 16K 372
Platform Requirements React React React, GraphQL Redux
Their Comparison (none) (none) Comparison
Supported Query Syntax Promise, REST, GraphQL Promise, REST, GraphQL GraphQL Promise, REST, GraphQL
Supported Frameworks React React React + Others Any
Supported Query Keys JSON JSON GraphQL Query JSON
Query Key Change Detection Deep Compare (Stable Serialization) Referential Equality (===) Deep Compare (Unstable Serialization) Referential Equality (===)
Query Data Memoization Level Query + Structural Sharing Query Query + Entity + Structural Sharing Query
Bundle Size 11.2KB 5.0KB 33.9KB 10.3KB
API Definition On-Use, Declarative On-Use GraphQL Schema Declarative
Queries
Caching
Devtools 🟡
Polling/Intervals
Parallel Queries
Dependent Queries
Paginated Queries
Infinite Queries 🛑
Bi-directional Infinite Queries 🔶 🔶 🛑
Infinite Query Refetching 🛑 🛑
Lagged Query Data1 🛑 🛑
Selectors 🛑
Initial Data
Scroll Recovery
Cache Manipulation
Outdated Query Dismissal
Render Optimization2 🛑 🛑
Auto Garbage Collection 🛑 🛑
Mutation Hooks 🟡
Offline Mutation Support 🛑 🟡 🛑
Prefetching APIs 🔶
Query Cancellation 🛑 🛑 🛑
Partial Query Matching3 🛑 🛑
Stale While Revalidate 🛑
Stale Time Configuration 🛑 🛑 🛑
Pre-usage Query/Mutation Configuration4 🛑 🛑
Window Focus Refetching 🛑 🛑
Network Status Refetching 🛑
General Cache Dehydration/Rehydration 🛑
Offline Caching ✅ (Experimental) 🛑 🔶
React Suspense (Experimental) 🛑 🛑
Abstracted/Agnostic Core 🛑
Automatic Refetch after Mutation5 🔶 🔶
Normalized Caching6 🛑 🛑 🛑

更多查看

總結

使用 React-Query 能夠更加高效的管理來自服務端的請求狀態,用更少的代碼實現較爲複雜的需求,讓你的狀態管理更優雅。

參考連接:

React-Query 官網

用 react-query 解決你一半的狀態管理問題

react-query