반응 무한 스크롤

48312 단어 reactjavascript

개요


본고에서 우리는 OpenLibrary에 http 요청을 보내고 조회와 일치하는 책 제목의 이름을 얻을 것입니다.그리고 이 결과는 페이지를 나누어 표시됩니다.사용자가 최종 문서로 스크롤할 때, 즉 화면에 나타날 때, 다음 문서를 가져옵니다.

도구


우리는 React 갈고리, 예를 들어 UseState,useffect,useRef,useCallback과 사용자 정의 갈고리를 사용할 것이다. 이 갈고리들은 http 요청을 생성하는 논리를 형성할 것이다.또한 Axios를 사용하면 이러한 논리를 단순화할 수 있습니다.

1단계 - 초기화


CodeSandbox로 이동하여 새로운 React 프로젝트를 초기화합니다. react.new

간단해.

2단계 - 요소


현재, 우리는 입력 필드를 표시할 수 있습니다. 일부div는 도서 제목을 표시하고, 두 개의 h3 표시는 불러오는 메시지와 오류 메시지를 표시합니다.
import React from "react";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <h1>React infinite scroll</h1>
      <input type="text" />
      <div>Book Title</div>
      <div>Book Title</div>
      <div>Book Title</div>
      <div>
        <h3>Loading...</h3>
      </div>
      <div>
        <h3>There seems to be an error</h3>
      </div>
    </div>
  );
}
그러면 다음과 같은 레이아웃이 제공됩니다.

이제 우리는 기능에 중점을 두고 뒤에 있는 댓글에 스타일을 추가할 것이다.

단계 3 - http 요청 보내기

useGetData.js에서 src이라는 파일을 만듭니다.우리는 또한 npm i axios을 통해axios를 설치할 수 있다.이제 react에서 UseState와 UseEffect를 가져오고, axios에서 axios를 가져옵니다.
import { useState, useEffect } from "react";
import axios from "axios";
이것이 바로 우리가 사용자 정의 연결 작업을 하기 위해 가져와야 할 모든 내용이다.
이제 함수를 정의합니다. 이 함수는 query 매개 변수와 pageNumber 매개 변수를 받아들여 loadingerror 상태를 나타내는 4개의 변수, 우리의 모든 책을 포함하는 books 수조와 hasMore 변수를 초기화합니다. 이 변수는 언제 결과의 끝에 도달할지 확인하고 API 호출을 중지합니다.
export default function useGetData(query, pageNumber) {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);
  const [books, setBooks] = useState([]);
  const [hasMore, setHasMore] = useState([]);
}
현재 우리는 query 매개 변수가 변경되었거나 pageNumber이 변경되었을 때만 UseEffect를 사용하여 API 호출을 할 수 있습니다.내부적으로 loadingtrue으로 설정하고 errorfalse으로 설정하고자 합니다.
useEffect(() => {
    setLoading(true);
    setError(false);
  }, [query, pageNumber]);

이 프로그램의 취지


현재, 우리는 axios를 통해 http 요청을 보낼 것입니다.우리는 요청을 취소하기 위해 cancel 변수를 설명할 것입니다. axios는 이 변수를 사용합니다.변경 사항을 조회할 때마다 요청을 보내는 것을 원하지 않기 때문에, 입력 필드에 새 문자를 입력할 때마다 요청을 보내는 것을 의미하기 때문이다.따라서 코드 효율이 떨어진다.해결 방안은 사용자가 입력을 끝낸 후에만 요청을 보내는 것이다.Axios는 이러한 이벤트가 발생했는지 쉽게 확인할 수 있습니다.
let cancel;
    axios({
      method: "GET",
      url: "https://openlibrary.org/search.json",
      params: { q: query, page: pageNumber },
      cancelToken: new axios.CancelToken((c) => (cancel = c))
    })
      .then((res) => {
        setBooks(prevBooks => {
          return [...new Set([...prevBooks, ...res.data.docs.map(b => b.title)])]
        })
        setHasMore(res.data.docs.length > 0)
        setLoading(false)
      })
      .catch((e) => {
        if (axios.isCancel(e)) return;
        setError(true)
      });
    return () => cancel();
보시다시피 cancelToken 키 이후의options 매개 변수 대상에 param이라는 추가 옵션을 전달해야 합니다.이것은 CancelToken을 되돌려줍니다. axios는 이 영패를 사용하여 요청을 취소합니다.
이 중 하나는 Mini-useEffect:
 useEffect(() => {
    setBooks([])
   }, [query])
사용자가 새 조회를 만든 후에 결과 목록을 재설정하려면 이 코드 세그먼트가 필요합니다.그렇지 않으면, 우리는 무한히 문서를 첨부할 것이며, 이전의 결과를 영원히 지우지 않을 것이다.
이 기능의 또 다른 관건은 우리의 catch 방법입니다.
catch((e) => {
        if (axios.isCancel(e)) return;
        setError(true)
      })
if문장을 어떻게 촉발하는지 주의하십시오. 이 문장은 axios.isCancel(e)true인지 false인지 평가합니다.이것은 키 변경이 감지되었는지 검사하여 요청을 취소하는 것과 같습니다.요청을 처리하고 오류를 받으면 setError(true)을 사용하여 오류 상태를 업데이트합니다.
또 다른 관건은 우리의 청소 기능: return () => cancel()이다.이 기능은 React의 UseEffect 연결로 제공되며, axios의 CancelToken 대상을 실행하는 함수를 사용할 수 있습니다.현재 요청은 끊임없이 얻은 후에만 처리됩니다.사용자가 상태 변경을 다시 입력하고 트리거하면 요청이 취소되고 미리 처리됩니다.

고기가 조금 더 있어요.


http 요청 결과를 건너뛰었습니다. 이제 처리하겠습니다. 다음은 성공적인 호출입니다.
then((res) => {
        setBooks(prevBooks => {
          return [...new Set([...prevBooks, ...res.data.docs.map(b => b.title)])]
        })
        setHasMore(res.data.docs.length > 0)
        setLoading(false)
      })
setState 함수 버전을 사용하면 이전 상태를 받아들이고 새 상태로 되돌아오는 함수를 설명합니다.되돌아오는 상태는 이전 서적의 비구조화 수조의 비구조화 집합과 각자의 서적 제목 필드를 추출한 후 얻은 문서의 비구조화 수조이다.알아요, 한 입.
이렇게 하는 이유는 우리가 중복된 책 이름을 가지고 있기 때문에 Set은 모든 중복값을 쉽게 필터할 수 있고 그 대가는 수조를 바꾸는 것이다.따라서 무결성을 유지하기 위해 이 어레이의 얕은 복사본이 필요합니다.새로운 상태는 현재 이전의 책 제목과 우리의 새로운 결과이다.
일단 우리가 결과가 나오면, 우리가 이미 결과의 종점에 도달했는지 검사할 때가 되었다.이를 위해 setHasMore(res.data.docs.length > 0)은true로 평가됩니다.우리가 어떻게 알았지?음, 검색된 데이터는 문서 그룹입니다. 만약 이 그룹의 길이가 0이라면, 우리는 우리가 이미 끝에 도달했다고 가정할 수 있습니다.
console.log(res.data)은 우리가 검색한 데이터를 보여 줍니다.

우리의 변수로 돌아가기


사용자 정의 갈고리 끝에 있는 return {loading, error, books, hasMore}은'전단'시각화 데이터에 필요한 모든 변수를 되돌려줍니다.
이것은 우리의 최종 useGetData.js:
import { useState, useEffect } from "react";
import axios from "axios";

export default function useGetData(query, pageNumber) {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);
  const [books, setBooks] = useState([]);
  const [hasMore, setHasMore] = useState(false);

  useEffect(() => {
    setBooks([])
   }, [query])

  useEffect(() => {
    setLoading(true)
    setError(false)
    let cancel;
    axios({
      method: "GET",
      url: "https://openlibrary.org/search.json",
      params: { q: query, page: pageNumber },
      cancelToken: new axios.CancelToken((c) => (cancel = c))
    })
      .then((res) => {
        setBooks(prevBooks => {
          return [...new Set([...prevBooks, ...res.data.docs.map(b => b.title)])]
        })
        console.log(res.data)
        setHasMore(res.data.docs.length > 0)
        setLoading(false)
      })
      .catch((e) => {
        if (axios.isCancel(e)) return;
        setError(true)
      });
    return () => cancel();
  }, [query, pageNumber]);

  return {loading, error, books, hasMore};
}

4단계 - 결과 표시

App.js으로 돌아가 다음 내용을 가져옵니다.
import React, { useState, useRef, useCallback } from "react";
import useGetData from "./useGetData";
import "./styles.css";
몇 가지 변수를 설명합니다.
const [query, setQuery] = useState("");
const [pageNumber, setPageNumber] = useState(1);
const { books, hasMore, loading, error } = useGetData(query, pageNumber);
우리의 query 변수는 조회 상태를 저장할 수 있도록 합니다.그리고 pageNumber을 1로 초기화하여 첫 페이지를 표시합니다.마지막으로, 우리는 사용자 정의 갈고리에서 검색된 변수를 나타내는 비구조화된 대상을 성명했다.주의, 갈고리를 정확하게 처리하기 위해서, 우리는 반드시 querypageNumber을 전송해야 한다.
이제 다음 코드를 작성합니다.
const observer = useRef();
  const lastBookElement = useCallback(
    (node) => {
      if (loading) return;
      if (observer.current) observer.current.disconnect();
      observer.current = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting && hasMore) {
          setPageNumber((prevPageNumber) => prevPageNumber + 1);
        }
      });
      if (node) observer.current.observe(node);
    },
    [loading, hasMore]
  );
보시다시피 const observer = useRef();을 사용하면 결과의 마지막 원소가 나타날 때 이 관찰자가 촉발된다는 것을 설명할 수 있습니다.다음 함수, 우리의 lastBookElement, useCallBack을 사용하여 다시 만드는 것을 방지합니다. loading 상태나 hasMore 로고 변경을 변경하지 않으면, [loading, hasMore]을 통해 의존항으로 추가합니다.
이제 useCallback 갈고리에서 HTML 노드 요소를 받습니다.우선, 만약에 loading의 계산 결과가true라면, 우리는 반드시 돌아가야 한다. 이것은 우리가 현재 최종 노드를 검측하고 싶지 않다는 것을 의미한다.다음 평가(if (observer.current) observer.current.disconnect();)는 관찰자를 현재 요소와 연결을 해제하여 새 문서 목록을 가져온 후 새 요소를 연결합니다.

사거리 관찰원


다음 코드 세그먼트는 인용된 노드가 창에 표시되는지, 더 많은 검색 결과가 있는지 확인하는 데 사용할 수 있습니다.
observer.current = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting && hasMore) {
          setPageNumber((prevPageNumber) => prevPageNumber + 1);
        }
      });
우리는 관찰기에 new IntersectionObserver을 분배했다. 함수를 매개 변수로 받아들이고 노드 항목 수조를 받아들이며 이 원소의 각종 속성, 예를 들어 isIntersecting을 되돌려준다. 이것은 우리가 필요로 하는 변수이다.일단 우리가 이 노드를 가시화할 수 있다면, 우리는 페이지 번호를 1 증가시킬 것이다.

계속 합시다.


function handleSearch(e) {
    setQuery(e.target.value);
    setPageNumber(1);
  }
우리는 현재 handleSearch 함수를 성명합니다. 이 함수는 querypageNumber을 업데이트할 것입니다.
마지막으로 HTML 구성 요소로 돌아갑니다.
return (
    <div className="App">
      <input type="text" value={query} onChange={handleSearch}></input>
      {books.map((book, index) => {
        if (books.length === index + 1) {
          return (
            <div ref={lastBookElement} key={book}>
              {book}
            </div>
          );
        } else {
          return (
            <div key={book}>
              <h3>{book}</h3>
            </div>
          );
        }
      })}
      {loading && (
        <div>
          <h3>Loading...</h3>
        </div>
      )}
      {error && (
        <div>
          <h3>There seems to be an error</h3>
        </div>
      )}
    </div>
  );
먼저 입력 요소를 다음과 같이 업데이트합니다.
<input type="text" value={query} onChange={handleSearch}>
이제 값을 추적하고 onChange 메서드를 추가합니다.
다음 단계에서 우리는 우리의 결과를 그릴 것이다.
{books.map((book, index) => {
        if (books.length === index + 1) {
          return (
            <div ref={lastBookElement} key={book}>
              {book}
            </div>
          );
        } else {
          return (
            <div key={book}>
              {book}
            </div>
          );
        }
      })}
우리가 마지막 원소 ref에 있을 때, 우리는 어떻게 독점 방식으로 (books.length === index + 1) 속성을 부가하는지 주의하십시오.그렇지 않으면 ref 속성이 없는 요소를 되돌려줍니다.
이제 로드와 오류 요소를 적절하게 표시할 수 있습니다.
{loading && (
        <div>
          <h3>Loading...</h3>
        </div>
      )}
      {error && (
        <div>
          <h3>There seems to be an error</h3>
        </div>
      )}
이것은 우리의 최종 App.js:
import React, { useState, useRef, useCallback } from "react";
import useGetData from "./useGetData";
import "./styles.css";

export default function App() {
  const [query, setQuery] = useState("");
  const [pageNumber, setPageNumber] = useState(1);
  const { books, hasMore, loading, error } = useGetData(query, pageNumber);

  const observer = useRef();
  const lastBookElement = useCallback(
    (node) => {
      if (loading) return;
      if (observer.current) observer.current.disconnect();
      observer.current = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting && hasMore) {
          setPageNumber((prevPageNumber) => prevPageNumber + 1);
        }
      });
      if (node) observer.current.observe(node);
    },
    [loading, hasMore]
  );

  function handleSearch(e) {
    setQuery(e.target.value);
    setPageNumber(1);
  }

  return (
    <div className="App">
      <input type="text" value={query} onChange={handleSearch}></input>
      {books.map((book, index) => {
        if (books.length === index + 1) {
          return (
            <div ref={lastBookElement} key={book}>
              {book}
            </div>
          );
        } else {
          return (
            <div key={book}>
              <h3>{book}</h3>
            </div>
          );
        }
      })}
      {loading && (
        <div>
          <h3>Loading...</h3>
        </div>
      )}
      {error && (
        <div>
          <h3>There seems to be an error</h3>
        </div>
      )}
    </div>
  );
}

결과


질의 가져오기:

도착 종점:

기능 데모: React Infinite Scroll - Carlos Z.

좋은 웹페이지 즐겨찾기