翻译在 React Hooks 中如何请求数据?

Posted 一零二四驿站

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了翻译在 React Hooks 中如何请求数据?相关的知识,希望对你有一定的参考价值。

通过这个教程,我想告诉你在 React 中如何使用 state 和 effect 这两种 hooks 去请求数据。我们将使用总所周知的 Hacker News API 来获取一些热门文章。你将定义属于你自己的数据请求的 Hooks ,并且可以在你所有的应用中复用,也可以发布到 npm 。

如果你不了解 React 的这些新特性,可以查看我的另一篇文章 introduction to React Hooks。如果你想直接查看文章的示例,可以直接 checkout 这个 Github 仓库。

注意:在 React 未来的版本中,Hooks 将不会用了获取数据,取而代之的是一种叫做 Suspense 的东西。尽管如此,下面的方法依然是了解 state 和 effect 两种 Hooks 的好方法。

使用 React Hooks 进行数据请求

如果你没有过在 React 中进行数据请求的经验,可以阅读我的文章:How to fetch data in React。文章讲解了如何使用 Class components 获取数据,如何使用可重用的 Render Props Components 和 Higher Order Components ,以及如何进行错误处理和 loading 状态。在本文中,我想用 Function components 和 React Hooks 来重现这一切。

 
   
   
 
  1. import React, { useState } from 'react';


  2. function App() {

  3. const [data, setData] = useState({ hits: [] });


  4. return (

  5. <ul>

  6. {data.hits.map(item => (

  7. <li key={item.objectID}>

  8. <a href={item.url}>{item.title}</a>

  9. </li>

  10. ))}

  11. </ul>

  12. );

  13. }


  14. export default App;

App 组件将展示一个列表,列表信息来自 Hacker News articles 。状态和状态更新函数将通过被称为 useState 的状态钩子来生成,它负责管理通过请求得到的 App 组件的本地状态。初始状态是一个空数组,目前没有任何地方给它设置新的状态。

我们将使用 axios 来获取数据,当然也可以使用你熟悉的请求库,或者浏览器自带的 fetch API。如果你还没有安装过 axios ,可以通过 npm install axios 进行安装。

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

  2. import axios from 'axios';


  3. function App() {

  4. const [data, setData] = useState({ hits: [] });


  5. useEffect(async () => {

  6. const result = await axios(

  7. 'http://hn.algolia.com/api/v1/search?query=redux',

  8. );


  9. setData(result.data);

  10. });


  11. return (

  12. <ul>

  13. {data.hits.map(item => (

  14. <li key={item.objectID}>

  15. <a href={item.url}>{item.title}</a>

  16. </li>

  17. ))}

  18. </ul>

  19. );

  20. }


  21. export default App;

我们在 useEffect 这个 effect hook 中,通过 axios 从 API 中获取数据,并使用 state hook 的更新函数,将数据存入到本地 state 中。并且使用 async/await 来解析promise。

然而,当你运行上面的代码的时候,你会陷入到该死的死循环中。effect hook 在组件 mount 和 update 的时候都会执行。因为我们每次获取数据后,都会更新 state,所以组件会更新,并再次运行 effect,这会一次又一次的请求数据。很明显我们需要避免这样的bug产生,我们只想在组件 mount 的时候请求数据。你可以在 effect hook 提供的第二个参数中,传入一个空数组,这样做可以避免组件更新的时候执行 effect hook ,但是组件在 mount 依然会执行它。

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

  2. import axios from 'axios';


  3. function App() {

  4. const [data, setData] = useState({ hits: [] });


  5. useEffect(async () => {

  6. const result = await axios(

  7. 'http://hn.algolia.com/api/v1/search?query=redux',

  8. );


  9. setData(result.data);

  10. }, []);


  11. return (

  12. <ul>

  13. {data.hits.map(item => (

  14. <li key={item.objectID}>

  15. <a href={item.url}>{item.title}</a>

  16. </li>

  17. ))}

  18. </ul>

  19. );

  20. }


  21. export default App;

第二个参数是用来定义 hook 所以依赖的变量的。如果其中一个变量发生变化,hook 将自动运行。如果第二个参数是一个空数组,那么 hook 将不会在组件更新是运行,因为它没有监控任何的变量。

还有一个需要特别注意的点,在代码中,我们使用了 async/await 来获取第三方 API 提供的数据。根据文档,每一个 async 函数都将返回一个隐式的 promise:

"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 函数定义了一个异步函数,它返回的是一个异步函数对象,异步函数是一个通过事件循环进行操作的函数,使用隐式的 Promise 返回最终的结果。”

然而,effect hook 应该是什么也不返回的,或者返回一个 clean up 函数的。这就是为什么你会在控制台看到一个错误信息。

 
   
   
 
  1. index.js:1452 Warning: useEffect function must return a cleanup function or nothing.

  2. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect.

这意味着我们不能直接在 useEffect 函数使用async。让我们来实现一个解决方案,能够在 effect hook 中使用 async 函数。

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

  2. import axios from 'axios';


  3. function App() {

  4. const [data, setData] = useState({ hits: [] });


  5. useEffect(() => {

  6. const fetchData = async () => {

  7. const result = await axios(

  8. 'http://hn.algolia.com/api/v1/search?query=redux',

  9. );


  10. setData(result.data);

  11. };


  12. fetchData();

  13. }, []);


  14. return (

  15. <ul>

  16. {data.hits.map(item => (

  17. <li key={item.objectID}>

  18. <a href={item.url}>{item.title}</a>

  19. </li>

  20. ))}

  21. </ul>

  22. );

  23. }


  24. export default App;

这就是一个使用 React Hooks 进行数据请求的小案例。但是,如果你对错误处理、loading 态、如何触发表单数据获取以及如何复用出具处理 hook 感兴趣,那我们接着往下看。

如何手动或者自动触发一个 hook?

现在我们已经能够在组件 mount 之后获取到数据,但是,如何使用输入框动态告诉 API 选择一个感兴趣的话题呢?可以看到之前的代码,我们默认将 "Redux" 作为查询参数('http://hn.algolia.com/api/v1/search?query=redux'),但是我们怎么查询关于 React 相关的话题呢?让我们实现一个 input 输入框,可以获得除了 “Redux” 之外的其他的话题。现在,让我们为输入框引入一个新的 state。

 
   
   
 
  1. import React, { Fragment, useState, useEffect } from 'react';

  2. import axios from 'axios';


  3. function App() {

  4. const [data, setData] = useState({ hits: [] });

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


  6. useEffect(() => {

  7. const fetchData = async () => {

  8. const result = await axios(

  9. 'http://hn.algolia.com/api/v1/search?query=redux',

  10. );


  11. setData(result.data);

  12. };


  13. fetchData();

  14. }, []);


  15. return (

  16. <Fragment>

  17. <input

  18. type="text"

  19. value={query}

  20. onChange={event => setQuery(event.target.value)}

  21. />

  22. <ul>

  23. {data.hits.map(item => (

  24. <li key={item.objectID}>

  25. <a href={item.url}>{item.title}</a>

  26. </li>

  27. ))}

  28. </ul>

  29. </Fragment>

  30. );

  31. }


  32. export default App;

现在,请求数据和查询参数两个 state 相互独立,但是我们需要像一个办法希望他们耦合起来,只获取输入框输入的参数指定的话题文章。通过以下修改,组件应该在 mount 之后按照查询获取相应文章。

 
   
   
 
  1. ...


  2. function App() {

  3. const [data, setData] = useState({ hits: [] });

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


  5. useEffect(() => {

  6. const fetchData = async () => {

  7. const result = await axios(

  8. `http://hn.algolia.com/api/v1/search?query=${query}`,

  9. );


  10. setData(result.data);

  11. };


  12. fetchData();

  13. }, []);


  14. return (

  15. ...

  16. );

  17. }


  18. export default App;

实际上,我们还缺少部分代码。你会发现当你在输入框输入内容后,并没有获取到新的数据。这是因为 useEffect 的第二个参数只是一个空数组,此时的 effect 不依赖于任何的变量,所以这只会在 mount 只会触发一次。但是,现在我们需要依赖查询条件,一旦查询发送改变,数据请求就应该再次触发。

 
   
   
 
  1. ...


  2. function App() {

  3. const [data, setData] = useState({ hits: [] });

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


  5. useEffect(() => {

  6. const fetchData = async () => {

  7. const result = await axios(

  8. `http://hn.algolia.com/api/v1/search?query=${query}`,

  9. );


  10. setData(result.data);

  11. };


  12. fetchData();

  13. }, [query]);


  14. return (

  15. ...

  16. );

  17. }


  18. export default App;

好了,现在一旦你改变输入框内容,数据就会重新获取。但是现在又要另外一个问题:每次输入一个新字符,就会触发 effect 进行一次新的请求。那么我们提供一个按钮来手动触发数据请求呢?

 
   
   
 
  1. function App() {

  2. const [data, setData] = useState({ hits: [] });

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

  4. const [search, setSearch] = useState('redux');


  5. useEffect(() => {

  6. const fetchData = async () => {

  7. const result = await axios(

  8. `http://hn.algolia.com/api/v1/search?query=${search}`,

  9. );


  10. setData(result.data);

  11. };


  12. fetchData();

  13. }, [search]);


  14. return (

  15. <Fragment>

  16. <input

  17. type="text"

  18. value={query}

  19. onChange={event => setQuery(event.target.value)}

  20. />

  21. <button type="button" onClick={() => setSearch(query)}>

  22. Search

  23. </button>

  24. <ul>

  25. {data.hits.map(item => (

  26. <li key={item.objectID}>

  27. <a href={item.url}>{item.title}</a>

  28. </li>

  29. ))}

  30. </ul>

  31. </Fragment>

  32. );

  33. }

此外,search state 的初始状态也是设置成了与 query state 相同的状态,因为组件在 mount 的时候会请求一次数据,此时的结果也应该是反应的是输入框中的搜索条件。然而, search state 和 query state 具有类似的值,这看起来比较困惑。为什么不将真实的 URL 设置到 search state 中呢?

 
   
   
 
  1. function App() {

  2. const [data, setData] = useState({ hits: [] });

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

  4. const [url, setUrl] = useState(

  5. 'http://hn.algolia.com/api/v1/search?query=redux',

  6. );


  7. useEffect(() => {

  8. const fetchData = async () => {

  9. const result = await axios(url);


  10. setData(result.data);

  11. };


  12. fetchData();

  13. }, [url]);


  14. return (

  15. <Fragment>

  16. <input

  17. type="text"

  18. value={query}

  19. onChange={event => setQuery(event.target.value)}

  20. />

  21. <button

  22. type="button"

  23. onClick={() =>

  24. setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)

  25. }

  26. >

  27. Search

  28. </button>


  29. <ul>

  30. {data.hits.map(item => (

  31. <li key={item.objectID}>

  32. <a href={item.url}>{item.title}</a>

  33. </li>

  34. ))}

  35. </ul>

  36. </Fragment>

  37. );

  38. }

这就是通过 effect hook 获取数据的案例,你可以决定 effect 取决于哪个 state。在这个案例中,如果 URL 的 state 发生改变,则再次运行该 effect 通过 API 重新获取主题文章。

Loading 态 与 React Hooks

让我们在数据的加载过程中引入一个 Loading 状态。它只是另一个由 state hook 管理的状态。Loading state 用于在 App 组件中呈现 Loading 状态。

 
   
   
 
  1. import React, { Fragment, useState, useEffect } from 'react';

  2. import axios from 'axios';


  3. function App() {

  4. const [data, setData] = useState({ hits: [] });

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

  6. const [url, setUrl] = useState(

  7. 'http://hn.algolia.com/api/v1/search?query=redux',

  8. );

  9. const [isLoading, setIsLoading] = useState(false);


  10. useEffect(() => {

  11. const fetchData = async () => {

  12. setIsLoading(true);


  13. const result = await axios(url);


  14. setData(result.data);

  15. setIsLoading(false);

  16. };


  17. fetchData();

  18. }, [url]);


  19. return (

  20. <Fragment>

  21. <input

  22. type="text"

  23. value={query}

  24. onChange={event => setQuery(event.target.value)}

  25. />

  26. <button

  27. type="button"

  28. onClick={() =>

  29. setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)

  30. }

  31. >

  32. Search

  33. </button>


  34. {isLoading ? (

  35. <div>Loading ...</div>

  36. ) : (

  37. <ul>

  38. {data.hits.map(item => (

  39. <li key={item.objectID}>

  40. <a href={item.url}>{item.title}</a>

  41. </li>

  42. ))}

  43. </ul>

  44. )}

  45. </Fragment>

  46. );

  47. }


  48. export default App;

现在当组件处于 mount 状态或者 URL state 被修改时,调用 effect 获取数据,Loading 状态就会变成 true。一旦请求完成,Loading 状态就会再次被设置为 false。

错误处理与 React Hooks

通过 React Hooks 进行数据请求时,如何进行错误处理呢? 错误只是另一个使用 state hook 初始化的另一种状态。一旦出现错误状态,App 组件就可以反馈给用户。当使用 async/await 函数时,通常使用 try/catch 来进行错误捕获,你可以在 effect 中进行下面操作:

 
   
   
 
  1. ...


  2. const [isError, setIsError] = useState(false);


  3. useEffect(() => {

  4. const fetchData = async () => {

  5. setIsError(false);

  6. setIsLoading(true);


  7. try {

  8. const result = await axios(url);

  9. setData(result.data);

  10. } catch (error) {

  11. setIsError(true);

  12. }


  13. setIsLoading(false);

  14. };


  15. fetchData();

  16. }, [url]);


  17. return (

  18. <Fragment>

  19. ...

  20. {isError && <div>Something went wrong ...</div>}

  21. ...

  22. <Fragment>

  23. );

effect 每次运行都会重置 error state 的状态,这很有用,因为每次请求失败后,用户可能重新尝试,这样就能够重置错误。为了观察代码是否生效,你可以填写一个无用的 URL ,然后检查错误信息是否会出现。

使用表单进行数据获取

什么才是获取数据的正确形式呢?现在我们只有输入框和按钮进行组合,一旦引入更多的 input 元素,你可能想要使用表单来进行包装。此外表单还能够触发键盘的 “Enter” 事件。

 
   
   
 
  1. function App() {

  2. ...

  3. const doFetch = (evt) => {

  4. evt.preventDefault();

  5. setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);

  6. }

  7. return (

  8. <Fragment>

  9. <form

  10. onSubmit={ doFetch }

  11. >

  12. <input

  13. type="text"

  14. value={query}

  15. onChange={event => setQuery(event.target.value)}

  16. />

  17. <button type="submit">Search</button>

  18. </form>


  19. {isError && <div>Something went wrong ...</div>}


  20. ...

  21. </Fragment>

  22. );

  23. }

自定义 hook 获取数据

我们可以定义一个自定义的 hook,提取出所有与数据请求相关的东西,除了输入框的 query state,除此之外还有 Loading 状态、错误处理。还要确保返回组件中需要用到的变量。

 
   
   
 
  1. const useHackerNewsApi = () => {

  2. const [data, setData] = useState({ hits: [] });

  3. const [url, setUrl] = useState(

  4. 'http://hn.algolia.com/api/v1/search?query=redux',

  5. );

  6. const [isLoading, setIsLoading] = useState(false);

  7. const [isError, setIsError] = useState(false);


  8. useEffect(() => {

  9. const fetchData = async () => {

  10. setIsError(false);

  11. setIsLoading(true);


  12. try {

  13. const result = await axios(url);


  14. setData(result.data);

  15. } catch (error) {

  16. setIsError(true);

  17. }


  18. setIsLoading(false);

  19. };


  20. fetchData();

  21. }, [url]);


  22. const doFetch = () => {

  23. setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);

  24. };


  25. return { data, isLoading, isError, doFetch };

  26. }

现在,我们在 App 组件中使用我们的新 hook 。

 
   
   
 
  1. function App() {

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

  3. const { data, isLoading, isError, doFetch } = useHackerNewsApi();


  4. return (

  5. <Fragment>

  6. ...

  7. </Fragment>

  8. );

  9. }

接下来,在外部传递 URL 给 DoFetch 方法。

 
   
   
 
  1. const useHackerNewsApi = () => {

  2. ...


  3. useEffect(

  4. ...

  5. );


  6. const doFetch = url => {

  7. setUrl(url);

  8. };


  9. return { data, isLoading, isError, doFetch };

  10. };


  11. function App() {

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

  13. const { data, isLoading, isError, doFetch } = useHackerNewsApi();


  14. return (

  15. <Fragment>

  16. <form

  17. onSubmit={event => {

  18. doFetch(

  19. `http://hn.algolia.com/api/v1/search?query=${query}`,

  20. );


  21. event.preventDefault();

  22. }}

  23. >

  24. <input

  25. type="text"

  26. value={query}

  27. onChange={event => setQuery(event.target.value)}

  28. />

  29. <button type="submit">Search</button>

  30. </form>


  31. ...

  32. </Fragment>

  33. );

  34. }

初始的 state 也是通用的,可以通过参数简单的传递到自定义的 hook 中:

 
   
   
 
  1. import React, { Fragment, useState, useEffect } from 'react';

  2. import axios from 'axios';


  3. const useDataApi = (initialUrl, initialData) => {

  4. const [data, setData] = useState(initialData);

  5. const [url, setUrl] = useState(initialUrl);

  6. const [isLoading, setIsLoading] = useState(false);

  7. const [isError, setIsError] = useState(false);


  8. useEffect(() => {

  9. const fetchData = async () => {

  10. setIsError(false);

  11. setIsLoading(true);


  12. try {

  13. const result = await axios(url);


  14. setData(result.data);

  15. } catch (error) {

  16. setIsError(true);

  17. }


  18. setIsLoading(false);

  19. };


  20. fetchData();

  21. }, [url]);


  22. const doFetch = url => {

  23. setUrl(url);

  24. };


  25. return { data, isLoading, isError, doFetch };

  26. };


  27. function App() {

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

  29. const { data, isLoading, isError, doFetch } = useDataApi(

  30. 'http://hn.algolia.com/api/v1/search?query=redux',

  31. { hits: [] },

  32. );


  33. return (

  34. <Fragment>

  35. <form

  36. onSubmit={event => {

  37. doFetch(

  38. `http://hn.algolia.com/api/v1/search?query=${query}`,

  39. );


  40. event.preventDefault();

  41. }}

  42. >

  43. <input

  44. type="text"

  45. value={query}

  46. onChange={event => setQuery(event.target.value)}

  47. />

  48. <button type="submit">Search</button>

  49. </form>


  50. {isError && <div>Something went wrong ...</div>}


  51. {isLoading ? (

  52. <div>Loading ...</div>

  53. ) : (

  54. <ul>

  55. {data.hits.map(item => (

  56. <li key={item.objectID}>

  57. <a href={item.url}>{item.title}</a>

  58. </li>

  59. ))}

  60. </ul>

  61. )}

  62. </Fragment>

  63. );

  64. }


  65. export default App;

这就是使用自定义 hook 获取数据的方法,hook 本身对API一无所知,它从外部获取参数,只管理必要的 state ,如数据、 Loading 和错误相关的 state ,并且执行请求并将数据通过 hook 返回给组件。

用于数据获取的 Reducer Hook

目前为止,我们已经使用 state hooks 来管理了我们获取到的数据数据、Loading 状态、错误状态。然而,所有的状态都有属于自己的 state hook,但是他们又都连接在一起,关心的是同样的事情。如你所见,所有的它们都在数据获取函数中被使用。它们一个接一个的被调用(比如: setIsErrorsetIsLoading),这才是将它们连接在一起的正确用法。让我们用一个 Reducer Hook 将这三者连接在一起。

Reducer Hook 返回一个 state 对象和一个函数(用来改变 state 对象)。这个函数被称为分发函数(dispatch function),它分发一个 action,action 具有 type 和 payload 两个属性。所有的这些信息都在 reducer 函数中被接收,根据之前的状态提取一个新的状态。让我们看看在代码中是如何工作的:

 
   
   
 
  1. import React, {

  2. Fragment,

  3. useState,

  4. useEffect,

  5. useReducer,

  6. } from 'react';

  7. import axios from 'axios';


  8. const dataFetchReducer = (state, action) => {

  9. ...

  10. };


  11. const useDataApi = (initialUrl, initialData) => {

  12. const [url, setUrl] = useState(initialUrl);


  13. const [state, dispatch] = useReducer(dataFetchReducer, {

  14. isLoading: false,

  15. isError: false,

  16. data: initialData,

  17. });


  18. ...

  19. };

Reducer Hook 以 reducer 函数和一个初始状态对象作为参数。在我们的案例中,加载的数据、Loading 状态、错误状态都是作为初始状态参数,且不会发生改变,但是他们被聚合到一个状态对象中,由 reducer hook 管理,而不是单个 state hooks。

 
   
   
 
  1. const dataFetchReducer = (state, action) => {

  2. ...

  3. };


  4. const useDataApi = (initialUrl, initialData) => {

  5. const [url, setUrl] = useState(initialUrl);


  6. const [state, dispatch] = useReducer(dataFetchReducer, {

  7. isLoading: false,

  8. isError: false,

  9. data: initialData,

  10. });


  11. useEffect(() => {

  12. const fetchData = async () => {

  13. dispatch({ type: 'FETCH_INIT' });


  14. try {

  15. const result = await axios(url);


  16. dispatch({ type: 'FETCH_SUCCESS', payload: result.data });

  17. } catch (error) {

  18. dispatch({ type: 'FETCH_FAILURE' });

  19. }

  20. };


  21. fetchData();

  22. }, [url]);


  23. ...

  24. };

现在,在获取数据时,可以使用 dispatch 函数向 reducer 函数发送信息。使用 dispatch 函数发送的对象具有一个必填的 type 属性和一个可选的 payload 属性。type 属性告诉 reducer 函数需要转换的 state 是哪个,还可以从 payload 中提取新的 state。在这里只有三个状态转换:初始化数据过程,通知数据请求成功的结果,以及通知数据请求失败的结果。

在自定义 hook 的末尾,state 像以前一样返回,但是因为我们所有的 state 都在一个对象中,而不再是独立的 state ,所以 state 对象进行解构返回。这样,调用 useDataApi 自定义 hook 的人仍然可以 dataisLoadingisError:

 
   
   
 
  1. const useDataApi = (initialUrl, initialData) => {

  2. const [url, setUrl] = useState(initialUrl);


  3. const [state, dispatch] = useReducer(dataFetchReducer, {

  4. isLoading: false,

  5. isError: false,

  6. data: initialData,

  7. });


  8. ...


  9. const doFetch = url => {

  10. setUrl(url);

  11. };


  12. return { ...state, doFetch };

  13. };

最后我们还缺少 reducer 函数的实现。它需要处理三个不同的状态转换,分被称为 FEATCH_INITFEATCH_SUCCESSFEATCH_FAILURE。每个状态转换都需要返回一个新的状态。让我们看看使用 switch case 如何实现这个逻辑:

 
   
   
 
  1. const dataFetchReducer = (state, action) => {

  2. switch (action.type) {

  3. case 'FETCH_INIT':

  4. return { ...state };

  5. case 'FETCH_SUCCESS':

  6. return { ...state };

  7. case 'FETCH_FAILURE':

  8. return { ...state };

  9. default:

  10. throw new Error();

  11. }

  12. };

reducer 函数可以通过其参数访问当前状态和 dispatch 传入的 action。到目前为止,在 switch case 语句中,每个状态转换只返回前一个状态,析构语句用于保持 state 对象不可变(即状态永远不会被直接更改)。现在让我们重写一些当前 state 返回的属性,以便在每次转换时更改 一些 state:

 
   
   
 
  1. const dataFetchReducer = (state, action) => {

  2. switch (action.type) {

  3. case 'FETCH_INIT':

  4. return {

  5. ...state,

  6. isLoading: true,

  7. isError: false

  8. };

  9. case 'FETCH_SUCCESS':

  10. return {

  11. ...state,

  12. isLoading: false,

  13. isError: false,

  14. data: action.payload,

  15. };

  16. case 'FETCH_FAILURE':

  17. return {

  18. ...state,

  19. isLoading: false,

  20. isError: true,

  21. };

  22. default:

  23. throw new Error();

  24. }

  25. };

现在,每个状态转换(action.type决定)都返回一个基于先前 state 和可选 payload 的新状态。例如,在请求成功的情况下,payload 用于设置新 state 对象的 data 属性。

总之,reducer hook 确保使用自己的逻辑封装状态管理的这一部分。通过提供 action type 和可选 payload ,总是会得到可预测的状态更改。此外,永远不会遇到无效状态。例如,以前可能会意外地将 isLoadingisError 设置为true。在这种情况下,UI中应该显示什么? 现在,由 reducer 函数定义的每个 state 转换都指向一个有效的 state 对象。

在 Effect Hook 中中断数据请求

在React中,即使组件已经卸,组件 state 仍然会被被赋值,这是一个常见的问题。我在之前的文章中写过这个问题,它描述了如何防止在各种场景中为未挂载组件设置状态。让我们看看在自定义 hook 中,请求数据时如何防止设置状态:

 
   
   
 
  1. const useDataApi = (initialUrl, initialData) => {

  2. const [url, setUrl] = useState(initialUrl);


  3. const [state, dispatch] = useReducer(dataFetchReducer, {

  4. isLoading: false,

  5. isError: false,

  6. data: initialData,

  7. });


  8. useEffect(() => {

  9. let didCancel = false;


  10. const fetchData = async () => {

  11. dispatch({ type: 'FETCH_INIT' });


  12. try {

  13. const result = await axios(url);


  14. if (!didCancel) {

  15. dispatch({ type: 'FETCH_SUCCESS', payload: result.data });

  16. }

  17. } catch (error) {

  18. if (!didCancel) {

  19. dispatch({ type: 'FETCH_FAILURE' });

  20. }

  21. }

  22. };


  23. fetchData();


  24. return () => {

  25. didCancel = true;

  26. };

  27. }, [url]);


  28. const doFetch = url => {

  29. setUrl(url);

  30. };


  31. return { ...state, doFetch };

  32. };

每个Effect Hook都带有一个clean up函数,它在组件卸载时运行。clean up 函数是 hook 返回的一个函数。在该案例中,我们使用 didCancel 变量来让 fetchData 知道组件的状态(挂载/卸载)。如果组件确实被卸载了,则应该将标志设置为 true,从而防止在最终异步解析数据获取之后设置组件状态。

注意:实际上并没有中止数据获取(不过可以通过Axios取消来实现),但是不再为卸载的组件执行状态转换。由于 Axios 取消在我看来并不是最好的API,所以这个防止设置状态的布尔标志也可以完成这项工作。

以上是关于翻译在 React Hooks 中如何请求数据?的主要内容,如果未能解决你的问题,请参考以下文章

react自定义hooks-自动改变页面的title,Http请求hooks等..(持续更新)

React Hooks 快速入门:从一个数据请求开始

React hooks之useEffect

使用 React Hooks useEffect 发送 ajax 请求获取数据全攻略

使用 React Hooks 编写 REST API 时如何重用代码?

React Hooks 上的 Apollo 突变