专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

如何在react hooks中请求数据(译)

在本教程中,我会向您展示如何使用**state ****和 ****effect **在React with Hooks中获取数据。我们将使用Hacker News API从科技界获取热门文章。您还将实现用于数据获取的自定义hook,该hook 可在应用程序中的任何位置重用或作为独立节点程序包发布在npm上。

如果您对这个新的React功能一无所知,请查看React Hooks简介


使用 react hook请求数据

如果您不熟悉React中的数据获取,请查看我的这篇文章。 它会带您了解**如何使用React类组件获取数据,如何使用**** Render Prop ComponentsHigher-Order Components,**使其可重用,以及如何处理错误和处理loading状态。 在本文中,我想通过 函数式组件中的React Hooks向您展示这些内容。

import React, { useState } from 'react';

function App() {
  const [data, setData] = useState({ hits: [] });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

在App组件中,显示了一个list(hits= Hacker News文章)。 状态和状态更新功能来自名为useState的hook,该hook负责管理App组件获取的数据的本地状态。 初始状态是空白列表。

我们将使用axios来获取数据,但是由您决定使用另一个数据获取库或浏览器的本机获取API。 如果尚未安装axios,则可以在命令行上使用npm install axios进行安装。 然后为数据获取实现效果挂钩:

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

export default props => {
  const { } = props;
  const [data, setData] = useState({ hits: [] });

  useEffect(async () => {
    const result = await fetch('https://hn.algolia.com/api/v1/search?query=redux');
    const resOjson = await result.json();
    setData(resOjson)

  })

  return (<>
    <ul>
      {data.hits.map((item) => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  </>);
}

使用useEffect这个钩子函数从 API获取数据,使用useState这个钩子函数将获得数据,存储为state。
然而,在运行代码时,代码会陷入一个讨厌的循环。useEffect函数在组件安装时执行,但在组件更新时也会执行**。
因为我们在每次获取数据后都会设置状态(state),所以组件会更新。这就会导致useEffect函数再次执行。 它一次又一次地获取数据。那么怎么避免这个bug呢–我们只需要在组件安装时获取数据。 可以为****useEffect提供一个空数组作为第二个参数,以避免在组件更新时激活它,而仅在安装组件时激活它。**

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

export default props => {
  const { } = props;
  const [data, setData] = useState({ hits: [] });

  useEffect(async () => {
    const result = await fetch('https://hn.algolia.com/api/v1/search?query=redux');
    const resOjson = await result.json();
    setData(resOjson)

  }, [])

  return (<>
    <ul>
      {data.hits.map((item) => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  </>);
}

代码会出现如下警告

Warning: An effect function must not return anything besides a function, which is used for clean-up.

It looks like you wrote useEffect(async () => …) or returned a Promise. Instead, write the async function inside your effect and call it immediately:

useEffect(() => { async function fetchData() { // You can await here const response = await MyAPI.getData(someId); // … } fetchData(); }, [someId]); // Or [] if effect doesn’t need props or state

Learn more about data fetching with Hooks: fb.me/react-hooks… in Unknown (created by Hello) in div (created by Hello) in Hello

useEffect的第二个参数是一个数组类型的变量,这个变量表示 useEffct所依赖的所有变量集合。如果数组中任意一个变量变化,则 useEffect会再次执行。如果变量为空数组,则在组件更新时useEffect不会再次执行,因为他不必依赖任何变量

在代码中,我们使用async / await从第三方API获取数据, 根据MDN文档-async,描述

The async function declaration defines an asynchronous function, which returns an AsyncFunction object. An asynchronous function is a function which operates asynchronously via the event loop, using an implicit Promise to return its result.

async function** 用来定义一个返回 AsyncFunction 对象的异步函数,异步函数是指通过事件循环****异步执行的函数,它会通过一个隐式的 Promise 返回其结果**

但是我们的代码中,异步 useEffect函数没有返回任何内容,这就是为什么在控制台中会出现上面加粗红色的警告内容–

**useEffect函数必须返回清除函数,或者不返回任何内容。如果你想将 useEffect 写成异步函数,或者返回一个Promise, 你可以在 **useEffect中 再申明一个异步函数并立刻调用它

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

export default props => {
  const { } = props;
  const [data, setData] = useState({ hits: [] });

  useEffect(() => {
    const fetchData = async () => {
      const result = await fetch('https://hn.algolia.com/api/v1/search?query=redux');
      const resOjson = await result.json();
      setData(resOjson)
    }

    fetchData();

  }, [])

  return (<>
    <ul>
      {data.hits.map((item) => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  </>);
}

以上就是在react的函数式组件中获取数据的方法了,但是,如果你对 如何在hooks组件中处理 请求错误,处理loading状态,如何从表单中获取数据 以及 如何重用hooks函数组件感兴趣,请继续阅读


如何触发 hooks函数

我们已经可以在一个hooks函数中 处理数据请求的情况了,但是怎么给请求传递参数呢?
让我们来实现一个带输入框,并给接口 传递参数的组件

我们通过 (data,query)这两种状态,实现输入框改变的时候查询指定的数据(给useEffect函数传递第二个参数来实现这个功能 — react官方useEffect文档)

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

export default props => {
  const { } = props;
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await fetch(`https://hn.algolia.com/api/v1/search?query=${query}`);
      const resOjson = await result.json();
      setData(resOjson)
    }

    fetchData();

  }, [query])

  return (<>
    <input
      type="text"
      value={query}
      onChange={event => setQuery(event.target.value)}
    />
    <ul>
      {data.hits.map((item) => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  </>);
}

更改输入框的值后,就可以重新请求数据了。 但这带来了另一个问题:在输入框中每改变一个字符,都会触发数据请求。

输入框频繁触发请求-解决方式一

提供一个触发请求的按钮,从而手动触发钩子?

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

export default props => {
  const { } = props;
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [search, setSearch] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await fetch(`https://hn.algolia.com/api/v1/search?query=${search}`);
      const resOjson = await result.json();
      setData(resOjson)
    }

    fetchData();

  }, [search])

  return (<>
    <input
      type="text"
      value={query}
      onChange={event => setQuery(event.target.value)}
    />
    <button type="button" onClick={() => setSearch(query)}>
      Search
      </button>
    <ul>
      {data.hits.map((item) => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  </>);
}

search的初始状态也被设置为与query相同的状态–因为该组件还需要在第一次记加载完成时获取数据。 但是,具有类似的 search和query状态有点令人困惑。 为什么不将实际URL设置为状态来替换 search

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

export default props => {
  const { } = props;
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    `https://hn.algolia.com/api/v1/search?query=redux`,
  );

  useEffect(() => {
    const fetchData = async () => {
      const result = await fetch(url);
      const resOjson = await result.json();
      setData(resOjson)
    }

    fetchData();

  }, [url])

  return (<>
    <input
      type="text"
      value={query}
      onChange={event => setQuery(event.target.value)}
    />
    <button type="button" onClick={() => setUrl(`https://hn.algolia.com/api/v1/search?query=${query}`)}>
      Search
      </button>
    <ul>
      {data.hits.map((item) => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  </>);
}

输入框频繁触发请求-解决方式二

使用节流函数处理


在 hooks组件请求中使用loading状态

大多数场景中,数据处于请求中时应该出现一个加载的状态 。 在hooks组件中,这个加载状态也可以存储为一个state。

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

export default props => {
  const { } = props;
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    `https://hn.algolia.com/api/v1/search?query=redux`,
  );
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);
      const result = await fetch(url);
      const resOjson = await result.json();
      setData(resOjson);
      setIsLoading(false);
    }

    fetchData();

  }, [url])

  return (<>
    <input
      type="text"
      value={query}
      onChange={event => setQuery(event.target.value)}
    />
    <button type="button" onClick={() => setUrl(`https://hn.algolia.com/api/v1/search?query=${query}`)}>
      Search
      </button>
    {isLoading ? (
      <div>Loading ...</div>
    ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
  </>);
}


在React Hooks中捕获错误

当请求出现错误时,在react hooks中该如何处理呢? —- 一旦出现错误,App组件应立即为用户提供反馈。 我们通常使用try / catch块进行错误处理。

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

export default props => {
  const { } = props;
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    `https://hn.algolia.com/api/v1/search?query=redux`,
  );
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(null);
      setIsLoading(true);

      try {
        const result = await fetch(url);
        const resOjson = await result.json();
        // 故意写错
        a = a + f;
        setData(resOjson);

      } catch (error) {
        setIsError(`${error}`);
      }

      setIsLoading(false);

    }

    fetchData();

  }, [url])

  const renderList = () => {
    if (isError) {
      return <div>{isError}</div>
    }
    if (isLoading) {
      return <div>Loading ...</div>
    }
    return (
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    )
  }

  return (<>
    <input
      type="text"
      value={query}
      onChange={event => setQuery(event.target.value)}
    />
    <button type="button" onClick={() => setUrl(`https://hn.algolia.com/api/v1/search?query=${query}`)}>
      Search
      </button>

    {renderList()}
  </>);
}

每次useEffect运行时,都会重置错误状态。 这很有用,因为在失败的请求之后,用户可能想要再次尝试,这将重置错误。 您可以将URL更改为无效的内容。 然后检查是否显示错误消息。


在React Form中请求数据

在表单中如何获取数据呢?到目前为止,我们只有输入字段和按钮的组合。 引入更多输入元素后,您可能需要用form元素包裹它们。 而且,form表单还可以通过键盘上的“ Enter”来触发提交操作。

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

export default props => {
  const { } = props;
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    `https://hn.algolia.com/api/v1/search?query=redux`,
  );
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(null);
      setIsLoading(true);

      try {
        const result = await fetch(url);
        const resOjson = await result.json();
        // a = a + f;
        setData(resOjson);

      } catch (error) {
        setIsError(`${error}`);
      }

      setIsLoading(false);

    }

    fetchData();

  }, [url])

  const renderList = () => {
    if (isError) {
      return <div>{isError}</div>
    }
    if (isLoading) {
      return <div>Loading ...</div>
    }
    return (
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    )
  }

  return (<>
    <form
      onSubmit={() =>
        setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
      }
    >
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button type="submit">Search</button>
    </form>
    {renderList()}
  </>);
}

但是现在,单击“提交”按钮时,浏览器将重新加载,因为这是浏览器提交表单时的原生行为。 为了阻止默认提交,我们可以在React事件上调用一个函数。

 ....
<form  onSubmit={(event) => {
        setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);
        event.preventDefault();
      }
      }>
.....


用Hooks自定义请求

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

const useDataApi = (initialUrl, initialData) => {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(null);
      setIsLoading(true);

      try {
        const result = await fetch(url);
        const resOjson = await result.json();
        setData(resOjson);
      } catch (error) {
        setIsError(`${error}`);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return [{ data, isLoading, isError }, setUrl];
};

export default  useDataApi;

import React, { useState, useEffect, useReducer } from 'react'
import useDataApi from './useDataApi'

export default props => {
  const { } = props;

  const [query, setQuery] = useState('redux');

  const [{ data, isLoading, isError }, doFetch] = useDataApi(
    'https://hn.algolia.com/api/v1/search?query=redux',
    { hits: [] },
  );

  const renderList = () => {
    if (isError) {
      return <div>{isError}</div>
    }
    if (isLoading) {
      return <div>Loading ...</div>
    }
    return (
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    )
  }

  return (<>
    <form
      onSubmit={(event) => {
        doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`);
        event.preventDefault();
      }
      }>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button type="submit">Search</button>
    </form>
    {renderList()}
  </>);
}

自定义数据请求的Hooks已完成。 自定义的请求hooks本身对API一无所知。 它从外部接收所有参数,并且仅管理必要的状态,例如数据,加载和错误状态。 它执行请求,并将数据用作自定义数据获,然后将数据返回给组件。


在Hooks中终止请求

在React中,一个常见的问题是即使组件已经被卸载(例如,由于使用React Router导航),也会设置组件状态。 我之前在这里已经写过关于此问题的文章,它描述了如何在各种情况下防止未安装组件的设置state。 让我们看看如何防止在自定义钩子中为数据获取设置状态:

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

const useDataApi = (initialUrl, initialData) => {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(null);

  useEffect(() => {
    // 主要是这个变量
    let didCancel = false;

    const fetchData = async () => {
      setIsError(null);
      setIsLoading(true);

      try {
        const result = await fetch(url);
        const resOjson = await result.json();
        if (!didCancel) {
          setData(resOjson);
        }

      } catch (error) {
        if (!didCancel) {
          setIsError(`${error}`);
        }
      }

      setIsLoading(false);
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);

  return [{ data, isLoading, isError }, setUrl];
};

参考文档

1、 How to fetch data with React Hooks?

文章永久链接:https://tech.souyunku.com/31792

未经允许不得转载:搜云库技术团队 » 如何在react hooks中请求数据(译)

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们