iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0
Modern Web

React進階班,用typescript與jest製作自己的custom hooks庫系列 第 28

[Day 28] useIntersection實戰 做出infinite scroll吧

  • 分享至 

  • xImage
  •  

為了方便,我今天找了一段時間,找到一個不用api key的免費Api,並且可以在後面更換postId,我們就可以假設他是像page的東西,他每一個postId會給我5個Comment, 我們可以利用這個做出無限輪動去增加他postId來抓取更多資料

https://jsonplaceholder.typicode.com/comments?postId=1

我的習慣是先寫hook,所以我們就先來寫hook吧

首先可以先拿上面api試試看,可以看到他的型別長這樣

type CommentType = {
  postId: number;
  id: number;
  name: string;
  email: string;
  body: string;
};

接下來我們先來寫一個簡單的抓api function 並且存到state中,這應該不會太困難,記得我們[]要放進PostId,這樣我們增加或改變它的時候才能再次抓api

const [comments, setComments] = useState<CommentType[]>([]);
  const [postId, setPostId] = useState(1);
  const [loading, setLoading] = useState(false);
  useEffect(() => {
    async function fetchComments() {
      try {
        setLoading(true);
        const { data } = await axios.get(
          `https://jsonplaceholder.typicode.com/comments?postId=${postId}`
        );
        setComments((prev) => [...prev, ...data]);
      } catch (err) {
        console.log(err);
      } finally {
        setLoading(false);
      }
    }
    fetchComments();
  }, [postId]);

接下來是比較困難的了,如果不知道intersectionObserve API的,可以回去看之前那篇,裡面有莫力全大神講解的,我這邊就講解怎麼寫比較好

  1. const observer = useRef<IntersectionObserver | null>(null);

    這裡我們使用了 useRef 來創建一個**ref,這個ref**將持有我們的 IntersectionObserver 實例。初始值是 null,表示當前沒有 observer。

  2. const intersectioningRef = useCallback(

    useCallback 是一個 React Hook,它返回一個 memoized 的 callback。這意味著它不會在每次渲染時重新創建,只有當它的依賴改變時才會重新創建。

  3. (node) => {

    這是回調函數的主體。當你的 React component 的某個 DOM 元素被掛載或更新時,這個函數將被調用,並接收這個 DOM 元素作為參數 node

  4. if (loading) return;

    如果當前正在**loading**,就退出函數,不要做任何事情。

  5. if (observer.current) {

    檢查我們的 observer ref 是否有值。如果有,這表示我們之前已經創建了一個 IntersectionObserver 實例。

  6. observer.current.disconnect();

    如果有之前的 IntersectionObserver 實例,我們需要先中斷它的觀察。這是為了確保在掛載新的元素或更新元素時,之前的觀察不會影響新的觀察。

  7. observer.current = new IntersectionObserver((entries) => {

    創建一個新的 IntersectionObserver 實例,並將其賦值給我們的 observer ref。

  8. if (entries[0].isIntersecting) {

    當被觀察的元素(node)進入或離開 viewport 時,這個回調將被調用。entries 是一個陣列,包含所有被觀察的元素的信息。在這裡,我們只關心第一個元素。

  9. setPostId((prev) => prev + 1);

    如果被觀察的元素進入了 viewport,則增加 postId 的值,代表我們已經到最底了。

  10. if (node) observer.current.observe(node);

    如果 node 存在(即不是 nullundefined),則開始使用我們的 IntersectionObserver 來觀察它。

 const observer = useRef<IntersectionObserver | null>(null);
  const intersectioningRef = useCallback(
    (node) => {
      if (loading) return;
      if (observer.current) {
        observer.current.disconnect();
      }
      observer.current = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting) {
          setPostId((prev) => prev + 1);
        }
      });
      if (node) observer.current.observe(node);
    },
    [loading]
  );

完整程式碼:

import axios from "axios";
import { useCallback, useEffect, useRef, useState } from "react";

type CommentType = {
  postId: number;
  id: number;
  name: string;
  email: string;
  body: string;
};

export default function useGetComments() {
  const [comments, setComments] = useState<CommentType[]>([]);
  const [postId, setPostId] = useState(1);
  const [loading, setLoading] = useState(false);
  useEffect(() => {
    async function fetchComments() {
      try {
        setLoading(true);
        const { data } = await axios.get(
          `https://jsonplaceholder.typicode.com/comments?postId=${postId}`
        );
        setComments((prev) => [...prev, ...data]);
      } catch (err) {
        console.log(err);
      } finally {
        setLoading(false);
      }
    }
    fetchComments();
  }, [postId]);

  const observer = useRef<IntersectionObserver | null>(null);
  const intersectioningRef = useCallback(
    (node) => {
      if (loading) return;
      if (observer.current) {
        observer.current.disconnect();
      }
      observer.current = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting) {
          setPostId((prev) => prev + 1);
        }
      });
      if (node) observer.current.observe(node);
    },
    [loading]
  );

  return { loading, comments, intersectioningRef };
}

接下來要寫component了,這裡就比較簡單

我們只要在最後一個元素放進監聽與他底下loading就好,所以用comments.length - 1 === index 判斷,這就是hook好的地方,讓整個component變得很乾淨,很簡潔,UI與邏輯分開

import * as React from 'react'
import useGetComments from './useGetComments'

export default function IntersectionObserverExample() {
    const { loading, comments, intersectioningRef } = useGetComments()

    return (
        <div>
            {comments.map((data, index) => {
                if (comments.length - 1 === index) {
                    return (
                        <div ref={intersectioningRef}>
                            <p style={{ fontSize: '16px', fontWeight: 600 }}>現在的postId: {data.postId}</p>
                            <p style={{ fontSize: '14px', fontWeight: 500, color: 'blue' }}>我的Id: {data.id}</p>
                            <p style={{ fontSize: '14px', fontWeight: 500, color: 'blue' }}>我的name: {data.name}</p>
                            <p style={{ fontSize: '10px' }}>我的email: {data.email}</p>
                            <p style={{ fontSize: '8px' }}>我的body: {data.body}</p>
                            {loading ? <p style={{ fontSize: '30px', color: 'red' }}>loading...</p> : null}
                        </div>
                    )
                }
                return (
                    <div>
                        <p style={{ fontSize: '16px', fontWeight: 600 }}>現在的postId: {data.postId}</p>
                        <p style={{ fontSize: '14px', fontWeight: 500, color: 'blue' }}>我的Id: {data.id}</p>
                        <p style={{ fontSize: '14px', fontWeight: 500, color: 'blue' }}>我的name: {data.name}</p>
                        <p style={{ fontSize: '10px' }}>我的email: {data.email}</p>
                        <p style={{ fontSize: '8px' }}>我的body: {data.body}</p>
                    </div>
                )
            })}
        </div>
    )
}

上一篇
[Day 27] useLogin test
下一篇
[Day 29] 實戰useIntersection測試
系列文
React進階班,用typescript與jest製作自己的custom hooks庫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言