常规请求

  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. // 'https://hn.algolia.com/api/v1/search?query=redux',
  8. // );
  9. // setData(result.data);
  10. // }, []);
  11. useEffect(() => {
  12. (async () => {
  13. const result = await axios(
  14. "https://hn.algolia.com/api/v1/search?query=redux"
  15. );
  16. setData(result.data);
  17. })();
  18. }, []);
  19. return (
  20. <ul>
  21. {data.hits.map(item => (
  22. <li key={item.objectID}>
  23. <a href={item.url}>{item.title}</a>
  24. </li>
  25. ))}
  26. </ul>
  27. );
  28. }
  29. export default App;

7-13行中,会
index.js:1452 Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect..

因为useEffect返回nothing或者clean up function,而async返回的是一个promise,如果对async或者useEffect不熟悉的参见 文章 useEffect异步

第21行,如果去掉[],会发现无限循环的请求接口,这是因为请求接口之后,重新setData,导致重新渲染,重新执行该effect,如此循环……
传入一个空数组,只能在mount的时候请求一次,如果想表单某个值改变的时候出发请求,该怎么实现呢?

触发请求

  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. `https://hn.algolia.com/api/v1/search?query=${query}`,
  10. );
  11. setData(result.data);
  12. };
  13. fetchData();
  14. }, [query]);
  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;

如上,每当input输入值改变的时候,都会对应的触发请求,通过维护一个query的值作为useEffect的依赖。
每次输入哪怕是只改变一个字母,都会频繁请求,那么我们通过一个按钮来控制请求,

  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 [search, setSearch] = useState("redux");
  7. useEffect(() => {
  8. const fetchData = async () => {
  9. const result = await axios(
  10. `https://hn.algolia.com/api/v1/search?query=${search}`
  11. );
  12. setData(result.data);
  13. };
  14. fetchData();
  15. }, [search]);
  16. return (
  17. <Fragment>
  18. <input
  19. type="text"
  20. value={query}
  21. onChange={event => setQuery(event.target.value)}
  22. />
  23. <button type="button" onClick={() => setSearch(query)}>
  24. Search
  25. </button>
  26. <ul>
  27. {data.hits.map(item => (
  28. <li key={item.objectID}>
  29. <a href={item.url}>{item.title}</a>
  30. </li>
  31. ))}
  32. </ul>
  33. </Fragment>
  34. );
  35. }
  36. export default App;

维护两个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. const [url, setUrl] = useState(
  7. "https://hn.algolia.com/api/v1/search?query=redux"
  8. );
  9. useEffect(() => {
  10. const fetchData = async () => {
  11. const result = await axios(url);
  12. setData(result.data);
  13. };
  14. fetchData();
  15. }, [url]);
  16. return (
  17. <Fragment>
  18. <input
  19. type="text"
  20. value={query}
  21. onChange={event => setQuery(event.target.value)}
  22. />
  23. <button
  24. type="button"
  25. onClick={() =>
  26. setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
  27. }
  28. >
  29. Search
  30. </button>
  31. <ul>
  32. {data.hits.map(item => (
  33. <li key={item.objectID}>
  34. <a href={item.url}>{item.title}</a>
  35. </li>
  36. ))}
  37. </ul>
  38. </Fragment>
  39. );
  40. }
  41. export default App;

我们尝试封装URL,每当点击search的时候,会改变url的query,url变化,就会触发effect的执行.

Loading 和 Error Indicator

loading和error indicator

  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. "https://hn.algolia.com/api/v1/search?query=redux"
  8. );
  9. const [loading, setLoading] = useState(false);
  10. const [err, setError] = useState(false);
  11. useEffect(() => {
  12. setLoading(true);
  13. try {
  14. const fetchData = async () => {
  15. const result = await axios(url);
  16. setData(result.data);
  17. setLoading(false);
  18. };
  19. fetchData();
  20. } catch (err) {
  21. setError(true);
  22. }
  23. }, [url]);
  24. return (
  25. <Fragment>
  26. <input
  27. type="text"
  28. value={query}
  29. onChange={event => setQuery(event.target.value)}
  30. />
  31. <button
  32. type="button"
  33. onClick={() =>
  34. setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
  35. }
  36. >
  37. Search
  38. </button>
  39. {err && <div>Something goes wrong ... </div>}
  40. {loading ? (
  41. <div>Loading...</div>
  42. ) : (
  43. <ul>
  44. {data.hits.map(item => (
  45. <li key={item.objectID}>
  46. <a href={item.url}>{item.title}</a>
  47. </li>
  48. ))}
  49. </ul>
  50. )}
  51. </Fragment>
  52. );
  53. }
  54. export default App;

自定义Hook

封装自定义Hooks

  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. console.log(error);
  17. setIsError(true);
  18. }
  19. setIsLoading(false);
  20. };
  21. fetchData();
  22. }, [url]);
  23. return [{ data, isLoading, isError }, setUrl];
  24. };
  25. function App() {
  26. const [query, setQuery] = useState("redux");
  27. const [{ data, isLoading, isError }, doFetch] = useDataApi(
  28. "https://hn.algolia.com/api/v1/search?query=redux",
  29. { hits: [] }
  30. );
  31. return (
  32. <Fragment>
  33. <form
  34. onSubmit={event => {
  35. doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`);
  36. event.preventDefault();
  37. }}
  38. >
  39. <input
  40. type="text"
  41. value={query}
  42. onChange={event => setQuery(event.target.value)}
  43. />
  44. <button type="submit">Search</button>
  45. </form>
  46. {isError && <div>Something went wrong ...</div>}
  47. {isLoading ? (
  48. <div>Loading...</div>
  49. ) : (
  50. <ul>
  51. {data.hits.map(item => (
  52. <li key={item.objectID}>
  53. <a href={item.url}>{item.title}</a>
  54. </li>
  55. ))}
  56. </ul>
  57. )}
  58. </Fragment>
  59. );
  60. }
  61. export default App;

这就是自定义hook,自定义hook通常以use开头,返回数组。
hook接收参数,内部管理状态,返回data、loading、error。

useReducer中请求数据

  1. import React, { Fragment, useState, useEffect, useReducer } from "react";
  2. import axios from "axios";
  3. const dataFetchReducer = (state, action) => {
  4. switch (action.type) {
  5. case "FETCH_INIT":
  6. return {
  7. ...state,
  8. isLoading: true,
  9. isError: false
  10. };
  11. case "FETCH_SUCCESS":
  12. return {
  13. ...state,
  14. isLoading: false,
  15. isError: false,
  16. data: action.payload
  17. };
  18. case "FETCH_FAILURE":
  19. return {
  20. ...state,
  21. isLoading: false,
  22. isError: true
  23. };
  24. default:
  25. throw new Error();
  26. }
  27. };
  28. const useDataApi = (initialUrl, initialData) => {
  29. const [url, setUrl] = useState(initialUrl);
  30. const [state, dispatch] = useReducer(dataFetchReducer, {
  31. isLoading: false,
  32. isError: false,
  33. data: initialData
  34. });
  35. useEffect(() => {
  36. const fetchData = async () => {
  37. dispatch({ type: "FETCH_INIT" });
  38. try {
  39. const result = await axios(url);
  40. dispatch({ type: "FETCH_SUCCESS", payload: result.data });
  41. } catch (error) {
  42. dispatch({ type: "FETCH_FAILURE" });
  43. }
  44. };
  45. fetchData();
  46. }, [url]);
  47. return [state, setUrl];
  48. };
  49. function App() {
  50. const [query, setQuery] = useState("redux");
  51. const [{ data, isLoading, isError }, doFetch] = useDataApi(
  52. "https://hn.algolia.com/api/v1/search?query=redux",
  53. { hits: [] }
  54. );
  55. return (
  56. <Fragment>
  57. <form
  58. onSubmit={event => {
  59. doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`);
  60. event.preventDefault();
  61. }}
  62. >
  63. <input
  64. type="text"
  65. value={query}
  66. onChange={event => setQuery(event.target.value)}
  67. />
  68. <button type="submit">Search</button>
  69. </form>
  70. {isError && <div>Something went wrong ...</div>}
  71. {isLoading ? (
  72. <div>Loading...</div>
  73. ) : (
  74. <ul>
  75. {data.hits.map(item => (
  76. <li key={item.objectID}>
  77. <a href={item.url}>{item.title}</a>
  78. </li>
  79. ))}
  80. </ul>
  81. )}
  82. </Fragment>
  83. );
  84. }
  85. export default App;

取消请求

请求api的时候,如果组件unmount,也会触发setState 详见组件unmount取消setState
在hooks中如何避免组件unmount的时候setState呢?

  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. return [state, setUrl];
  29. };

重点第11行、19行、23行、31-33行。
通过didCancel标志来控制。

Reference:
https://www.robinwieruch.de/react-hooks-fetch-data