成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

如何使用 React Query 做下拉數據自動刷新?

開發 前端
本文我們講述了 React Query 中用于無限查詢 API useInfiniteQuery() 的使用。通過循序漸進的 3 個案例,最終實現了一個下拉到底后自動新數據的交互效果,還是比較好實現的。

useInfiniteQuery() API

看名字就能猜出來,useInfiniteQuery() 是專門用來應付無限查詢場景的。不僅如此,useInfiniteQuery() API 能力也是基于 useQuery() 的。

之前的文章中我們介紹了 useQuery() 的核心 API,為了找回印象,我們在此貼出來:

import { useQuery } from 'react-query'

const {
  data,
  error,
  isError,
  isFetching,
  isLoading,
  isRefetching,
  isSuccess,
  refetch,
} = useQuery(queryKey, queryFn?, {
  enabled,
  onError,
  onSuccess,
  refetchOnWindowFocus,
  retry,
  staleTime,
})

如果我們把這些 API 簡化如下:

const {
  ...result,
} = useQuery(queryKey, queryFn?, {
  ...options,
})

useInfiniteQuery() 其實就是在 useQuery() 基礎之上增添一些無限查詢場景的參數:

const {
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
  ...result
} = useInfiniteQuery(queryKey, ({ pageParam = 1 }) => fetchPage(pageParam), {
  ...options,
  getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})

如你所見,增加的 API 其實就是跟上一頁/下一頁查詢動作相關的參數,相比較于自己組裝 的分頁查詢能力的 useQuery(),useInfiniteQuery() 需要配置上一頁/下一頁的參數獲取函數,并提供了相應的查詢調用能力,更加自動化和便捷。

當然,增加的不只是參數,還有 2 處:

一個是 queryFn 參數的入參,多了一個名為 pageParam 的參數。

pageParam 表示當前頁數。這個值是每次 useInfiniteQuery() 調用時,通過 getNextPageParam()/getPreviousPageParam() 返回值自動獲取并傳入 queryFn 的。

第二個還有返回值的數據結構,即 data。

const { data } = useInfiniteQuery()

原來 data 就是表示內部請求方法的返回值。而 useInfiniteQuery() 的返回 data 因為要包含多頁數據(展示舊數據時,還要持有舊數據),因此 data 變更為:

data: { pages: TData[], pageParams: unknown[] }

pages 很好理解,就是用來承載過程中請求到的多頁數據;pageParams 則是每個頁面當時在做數據獲取時使用的查詢參數。

簡單一例

當然語言上說得再多,也是蒼白無力的,實踐出真知。這里我們就舉一個簡單的例子說明 useInfiniteQuery() 的使用。

首先,我們先創建一個獲取數據的請求函數(使用 Fetch API)。

const getPosts = async (pageParam) => {
  return fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam.page}&_limit=${pageParam.size}`).then(res => res.json())
}

接著,使用 useInfiniteQuery() 請求數據:

function Example() {
  const {
    isLoading,
    isError,
    error,
    data,
  } = useInfiniteQuery(
    'posts',
    ({ pageParam }) => getPosts(pageParam),
    {
      getNextPageParam: (lastPage, allPages) => ({ page: allPages.length + 1, size: 6 }),
      refetchOnWindowFocus: false, // Prevent refetching on window focus
    }
  )
  
  // ...
}

增加下加載中或出現異常時的處理邏輯。

function Example() {
  // ...
  if (isLoading) {
    return <div>Loading...</div>;
  }

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

最后渲染分頁數據。

function Example() {
  // ...

  return (
    <div>
      <ol>
        {/* (1) */}
        {data.pages.map((page) => (
          {page.map((post) => (
            <li key={post.id}>{post.title}</li>
          ))}
        ))}
      </ol>
      
      {/* (2) */}
      <button onClick={() => fetchNextPage()}>More</button>
    </div>
  )
}
  1. 遍歷 data.pages 中所有頁面數據,渲染出來
  2. 使用 fetchNextPage() 函數加載更多(實際上即“下一頁”)數據

瀏覽器訪問,不幸運是,報錯了。

圖片圖片

沒關系,點擊查看報錯處。

圖片圖片

發現是是由于首次請求接口時,pageParam 參數是 undefined,這就需要我們給 pageParam 一個默認值。

- const getPosts = async (pageParam) => {
+ const getPosts = async (pageParam = { page: 1, size: 6 }) => {
  return fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam.page}&_limit=${pageParam.size}`).then(res => res.json())
}

再次訪問頁面,發現我們成功請求到了第一頁數據。

圖片圖片

接下來點擊“More”按鈕,發現發起了到第二頁數據的請求。

圖片圖片

圖片圖片

這里邏輯是先調用 getNextPageParam() 函數獲得請求參數。getNextPageParam() 函數的類型聲明如下:

getNextPageParam: (lastPage, allPages) => unknown | undefined

第一個參數就是上一頁數據,第二個參數即到目前為止請求到的所有數據。對照本例,當我們第一次點擊“More”按鈕時,返回的值為 { page:2, size:6 }。然后,再發起 getPosts 調用并這個參數,于是我們便發起了第二頁的數據請求,并最終得到了數據。

下面,我們再給“More”按鈕增加一個加載狀態——我們可以使用通用的 isFetching 狀態,但這里我們使用更具體的 isFetchingNextPage 狀態。

function Example() {
  const {
    isLoading,
+   isFetchingNextPage,
    isError,
    error,
    data,
  } = useInfiniteQuery()

添加給按鈕。

- <button notallow={() => fetchNextPage()}>More</button>
+ <button disabled={isFetchingNextPage} notallow={() => fetchNextPage()}>More</button>

查看效果。

圖片圖片

這樣,我們就能在請求下一頁數據的時候禁用按鈕點擊,避免用戶誤觸。

再來一例

以上案例中,我們是通過 getNextPageParam() 所提供的第二個參數 allPages 來獲得下一頁頁碼的。

getNextPageParam: (lastPage, allPages) => ({ page: allPages.length + 1, size: 6 })

這是因為返回的第一頁數據總并未包含下一頁信息。

圖片圖片

我們再稍微修改下

const getPosts = async (pageParam = { page: 1, size: 6 }) => {
  return fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam.page}&_limit=${pageParam.size}`).then(res => {
    const total = res.headers.get('X-Total-Count')

    return res.json().then(data => {
      return {
        total,
        data,
        hasMore: pageParam.page * pageParam.size < total
      }
    })
  })
}

我們將返回數據放在 data 屬性中,并返回數據總數(total)以及是否還有數據(hasMore)。

有了這些信息,我們就可以有了更加細膩的交互了。

下面,修改渲染邏輯。

function Example() {
  // ...
  
  return (
    <div>
      <ol>
        {data.pages.map((page) => (
          <>
            {page.data.map((post) => (
              <li key={post.id}>{post.title}</li>
            ))}
          </>
        ))}
      </ol>
      <p>總共 <strong>{data.pages[0].total}</strong> 條數據</p>
      {
        data.pages[data.pages.length - 1].hasMore ? (
          <button disabled={isFetchingNextPage} onClick={() => fetchNextPage()}>More</button>
        ) : <span>--- 我是有底線的 ---</span>
      }
    </div>
  )
}

查看初始加載效果。

圖片圖片

查看最終效果。

圖片圖片

不過,按鈕顯隱邏輯還是有些冗余。

{
  data.pages[data.pages.length - 1].hasMore ? (
    <button disabled={isFetchingNextPage} onClick={() => fetchNextPage()}>More</button>
  ) : <span>--- 我是有底線的 ---</span>
}

這一點 useInfiniteQuery 也幫我們想到了,便提供了一個 hasNextPage 供我們使用。

{
-  data.pages[data.pages.length - 1].hasMore ? (
+  hasNextPage ? (
    <button disabled={isFetchingNextPage} notallow={() => fetchNextPage()}>More</button>
  ) : <span>--- 我是有底線的 ---</span>
}

hasNextPage 為 true 表示有下一頁數據,為 false 表示沒有數據了。

不過 hasNextPage 的值是受到 getNextPageParam() 返回值影響的——當 getNextPageParam() 返回 undefined 時,hasNextPage 值為 false,否則為 true。

因此,我們修改下 getNextPageParam() 的判斷邏輯。

getNextPageParam: (lastPage, allPages) => {
  return lastPage.hasMore 
    ? { page: allPages.length + 1, size: 50 } 
    : undefined
},

如此,我們再來查看下效果。

圖片圖片

發現沒有數據的時候,同樣也不會展示“More”按鈕了。大功告成!

下拉拉取新數據

當然,以上通過點擊“More”按鈕加載新數據的方式還是太麻煩了一些。如果我們在網頁下拉到底部的時候自動獲取數據不是更好嗎?

下面就來實現一下。

我們將借助 IntersectionObserver API[4] + 底部一個 .loadMore 元素來實現。

首先,將 “More” 按鈕部分的代碼替換成:

<div className="loadMore" style={{ height: '30px', lineHeight: '30px' }} ref={loadMoreRef}>
  {
    isFetchingNextPage 
      ? <span>Loading...</span> 
      : <span>--- 我是有底線的 ---</span>
  }
</div>

注意,我們同時通過 loadMoreRef 拿到 DOM 元素。

然后,我們監聽 .loadMore 元素,一旦出現在屏幕中,就調用 fetchNextPage() 獲取下一頁數據。

useEffect(() => {
  const observer = new IntersectionObserver((entries) => {
    if (entries[0].isIntersecting && hasNextPage) {
      fetchNextPage();
    }
  });

  if (loadMoreRef.current) {
    observer.observe(loadMoreRef.current);
  }

  return () => observer.disconnect();
}, [hasNextPage, fetchNextPage]);

來查看效果。

圖片圖片

完美。

最后,再把完整代碼貼出來,方便大家學習。

import { useEffect, useRef } from 'react'
import { QueryClient, QueryClientProvider, useInfiniteQuery } from 'react-query'

// Create a client
const queryClient = new QueryClient()

export default function App() {
  return (
    // Provide the client to your App
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  )
}

const getPosts = async (pageParam = { page: 1, size: 25 }) => {
  return fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam.page}&_limit=${pageParam.size}`).then(res => {
    const total = res.headers.get('X-Total-Count')

    return res.json().then(data => {
      return {
        total,
        data,
        hasMore: pageParam.page * pageParam.size < total
      }
    })
  })
}

function Example() {
  const {
    isLoading,
    isFetchingNextPage,
    hasNextPage,
    isError,
    error,
    data,
    fetchNextPage
  } = useInfiniteQuery(
    'posts',
    ({ pageParam }) => getPosts(pageParam),
    {
      getNextPageParam: (lastPage, allPages) => {
        return lastPage.hasMore ? { page: allPages.length + 1, size: 25 } : undefined
      },
      refetchOnWindowFocus: false, // Prevent refetching on window focus
    }
  )

  const loadMoreRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting && hasNextPage) {
        fetchNextPage();
      }
    });

    if (loadMoreRef.current) {
      observer.observe(loadMoreRef.current);
    }

    return () => observer.disconnect();
  }, [hasNextPage, fetchNextPage]);


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

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

  return (
    <div>
      <p>總共 <strong>{data.pages[0].total}</strong> 條數據</p>
      <ol>
        {data.pages.map((page) => (
          <>
            {page.data.map((post) => (
              <li key={post.id}>{post.title}</li>
            ))}
          </>
        ))}
      </ol>
      <div className="loadMore" style={{ height: '30px', lineHeight: '30px' }} ref={loadMoreRef}>
        {
          isFetchingNextPage ? <span>Loading...</span> : <span>--- 我是有底線的 ---</span>
        }
      </div>
    </div>
  )
}

總結

本文我們講述了 React Query 中用于無限查詢 API useInfiniteQuery() 的使用。

通過循序漸進的 3 個案例,最終實現了一個下拉到底后自動新數據的交互效果,還是比較好實現的。

當然,本文只是以“下一頁”舉例,“上一頁”與此同理。

希望本位講述的內容能夠對你的工作有所幫助。感謝閱讀,再見。

參考資料

[1]React Query 是做什么的?: https://juejin.cn/post/7378015213348257855

[2]一個數據獲竟然被 React Query 玩出這么多花樣來!: https://juejin.cn/post/7380342160581918731

[3]React Query 的 useQuery 竟也內置了分頁查詢支持!: https://juejin.cn/post/7380569775686746151

[4]IntersectionObserver API: https://ruanyifeng.com/blog/2016/11/intersectionobserver_api.html

責任編輯:武曉燕 來源: 寫代碼的寶哥
相關推薦

2024-10-14 08:39:25

2022-08-03 09:11:31

React性能優化

2019-11-22 08:40:19

ProtobufGo編程語言

2015-04-22 10:57:22

androidSwipeRefres

2013-07-17 16:33:02

下拉刷新listvie滾動到底部加載Android開發學習

2019-10-08 11:10:18

React自動保存前端

2023-06-16 09:08:39

ReactContextRFC

2023-11-27 08:24:57

FormikReact

2022-03-30 09:43:19

jscodeshif自動化重構開發

2015-03-23 18:11:39

UITableViewswift下拉刷新

2016-08-05 17:01:09

AndroidRecyclerVie下拉刷新

2023-05-26 06:30:56

2012-04-12 10:19:08

Ajax.NET

2021-03-12 18:25:09

開發前端React

2022-07-18 09:01:58

React函數組件Hooks

2021-06-01 09:27:52

視頻動畫Remotion

2023-12-01 09:18:27

AxiosAxios 庫

2022-04-14 08:00:00

Cypress測試開發

2015-05-13 09:36:18

js模擬手機下拉刷新

2021-12-01 10:02:57

鴻蒙HarmonyOS應用
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 911精品国产 | 国产日韩精品视频 | 国产一级片免费看 | 鲁一鲁资源影视 | 欧美日韩精品 | a免费视频 | 日韩中文av在线 | 国产伦精品一区二区三区高清 | 蜜桃视频在线观看免费视频网站www | 久久国产精99精产国高潮 | 国产污视频在线 | 欧美男人的天堂 | 久久精品91 | 亚洲男女视频在线观看 | 成人二区 | 日韩成人一区二区 | 99re视频在线观看 | 五月激情六月婷婷 | 欧美自拍视频 | 欧美国产视频 | 中文字幕在线观看一区 | 日本手机在线 | 欧美jizzhd精品欧美巨大免费 | 日本高清中文字幕 | 国产成人午夜电影网 | 三a毛片| 性欧美精品一区二区三区在线播放 | 成人三级影院 | 亚洲成av人片在线观看无码 | 亚洲网站在线播放 | 久久不卡日韩美女 | 国产精品欧美精品 | 亚洲天天干| 国产精品久久久久久久久久软件 | 亚洲国产aⅴ成人精品无吗 欧美激情欧美激情在线五月 | 欧美a在线 | 日韩精品一区二区三区中文字幕 | 国产一区二区日韩 | 亚洲精品视频免费观看 | 久草电影网 | 在线三级网址 |