在 useEffect 挂钩中请求未知数量的 API 页面的最佳方法

IT技术 reactjs react-hooks use-effect
2021-05-23 10:50:55

我正在尝试编写一个 React 功能组件,它请求一个 API 中跨多个页面公开的人员列表:

https://swapi.dev/api/people/?page=9

我知道目前有九个页面,但这在未来可能会改变,所以我希望我的组件继续请求页面,直到没有更多页面,并在"results"每个响应主体位中积累和存储人员我正在尝试使用useStateuseEffect钩子来做到这一点

是否有解决这个问题的通用方法,通常被认为是最好的方法?有人可以告诉我如何修改我现有的代码以使其以最佳方式工作吗?

我当前的代码导致浏览器无休止地加载。

在第一次也是唯一一次执行中尝试处理整个过程是不是我错了useEffect我是否应该useState有一个依赖数组被传递,page以便useEffect每次page更新时再次执行

import React, { useState, useEffect } from 'react';

function GetAllPeople() {
  const [page, setPage] = useState(1)
  const [people, setPeople] = useState([]);
  const [gotEveryone, setGotEveryone] = useState(false)
  const [error, setError] = useState(null)

  const requestData = () => (
    fetch(`https://swapi.dev/api/people/?page=${page}`)
      .then(res => {
        if (res.ok) {
          let response = res.json()
          setPeople(people.concat(response.results))
          setPage(page + 1)
        } else if(res.status === 404) {
          setGotEveryone(true)
        } else {
          setError(res.status)
          return Promise.reject('some other error: ' + res.status)
        }
    })
  )

  useEffect(() => {
    while(gotEveryone === false) {
      if (!error) {
        requestData()
      }
    }
  })

  return (
    <div>
      <ul>
        {people.map((person, index) =>
          <li key={index}>
            {person.name}
          </li>
        )}
      </ul>
    </div>
  )
}

export default GetAllPeople;
4个回答

不必要的钩子.. 使用 api 数据(递归调用)

我制作了一个带有模拟数据的代码沙盒来保存 api 请求(用于开发)

https://codesandbox.io/s/elastic-field-i89yu

但主要思想在这里:

在 api 响应中使用下一个键: 在此处输入图片说明

你可以让它更优雅:

function GetAllPeople() {
  const [people, setPeople] = useState([]);

  const requestData = useCallback((url = 'https://swapi.dev/api/people/?page=1') => (
    fetch(url)
      .then(async res => {
        const response = await res.json();
        setPeople(prevState => prevState.concat(response.results));
        if (response.next) {
          requestData(response.next.replace("http://", "https://"));
        } else {
          console.log("Finish !");
        }
      }).catch(err => {
        throw err;
      })
  ), []);

  useEffect(() => {
    requestData();
  }, [requestData]);

  return (
    ...
  )
}

披露:我是悬疑服务的图书馆作者

使用 React Suspense 进行数据获取,并基于Omer提供的建议,您可以通过创建有状态服务来迭代每个 API 端点来加载结果:

const { useEffect, useState } = React;
const { createService, useServiceState } = SuspenseService;

const FetchJSON = createService((url) => (
  fetch(url).then((res) => res.json())
));

function useFetch() {
  return useServiceState(FetchJSON);
}

function People() {
  const [data, setUrl] = useFetch();
  const [people, setPeople] = useState([]);

  useEffect(() => {
    const { next, results } = data;
    if (next) setUrl(next);
    setPeople(prev => prev.concat(results));
  }, [data]);

  const list = people.map(({ name, url }) =>
    <li key={url}>
      {name}
    </li>
  );

  return (
    <div>
      <ul>
        {list}
      </ul>
    </div>
  );
}

function App() {
  return (
    <FetchJSON.Provider
      request="https://swapi.dev/api/people/?page=1"
      fallback={<p>Loading...</p>}
    >
      <People />
    </FetchJSON.Provider>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/suspense-service@0.2.6/dst/umd/suspense-service.js"></script>
<div id="root"></div>

如果启用并发模式,您甚至可以对结果进行分页,并使用响应数据呈现功能正常的“上一个”和“下一个”按钮:

使用一个依赖数组,page以便requestData在挂载时唯一调用,并且在前一页的响应返回之后。不要使用while- 这会阻塞,完全阻止应用程序加载。

您还需要修复您提出的请求 -.json返回一个 Promise,您需要在访问结果值之前等待它解决。

const { useState, useEffect } = React;
function App() {
    const [page, setPage] = useState(1)
    const [people, setPeople] = useState([]);
    const [gotEveryone, setGotEveryone] = useState(false)
    const [error, setError] = useState(null)

    useEffect(() => {
        if (!gotEveryone && !error) {
            fetch(`https://swapi.dev/api/people/?page=${page}`)
              .then(res => {
                  if (res.status === 404) setGotEveryone(true);
                  else return res.json()
                      .then((result) => {
                          console.log('got result', page);
                          // if people or page could be set elsewhere,
                          // use the callback form instead below
                          setPeople(people.concat(result.results));
                          setPage(page + 1);
                      });
              })
              .catch((err) => {
                  setError(res.err);
              })
        }
    }, [page]);

    return (
        <div>
            <ul>
                {people.map((person, index) =>
                    <li key={index}>
                        {person.name}
                    </li>
                )}
            </ul>
        </div>
    )
}

ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

如果您倾向于在没有分页或更多加载的情况下一次性加载组件中的所有人,这是另一种方法:

  const requestData = async (page, result = []) => {
    const response = await fetch(`https://swapi.dev/api/people/?page=${page}`);
    let data = await response.json();
    if (response.status === 200) {
      result.push(...data.results);
      return requestData(++page, result);
    }

    console.log(result); // all people
    return data;
  };

  useEffect(() => {
    requestData(1, []);
  }, []);

您调用requestData一次,useEffect然后您使用不同的页面递归调用它,并将结果保存在其中,每次都不会发生任何状态突变,并且在最后一次调用中您安全地设置状态。

这不是您问题的完整代码,而是另一种方法,您可以编辑此代码,以更改页面或设置错误。