在本教程中,我想向你展示如何使用 state 和 effect 钩子在React中获取数据。 你还将实现自定义的 hooks 来获取数据,可以在应用程序的任何位置重用,也可以作为独立节点包在npm上发布。
如果你对 React 的新功能一无所知,可以查看 React hooks 的相关 api 介绍。如果你想查看完整的如何使用 React Hooks 获取数据的项目代码,可以查看 github 的仓库
如果你只是想用 React Hooks 进行数据的获取,直接 npm i use-data-api
并根据文档进行操作。如果你使用他,别忘记给我个star 哦~
注意:将来,React Hooks 不适用于 React 中获取数据。一个名为Suspense的功能将负责它。以下演练是了解React中有关 state 和 Effect hooks 的更多信息的好方法。
如果您不熟悉React中的数据提取,请查看我在React文章中提取的大量数据。它将引导您完成使用React类组件的数据获取,如何使用Render Prop 组件和高阶组件来复用这些数据,以及它如何处理错误以及 loading 的。
1 import React, { useState } from 'react';
2
3 function App() {
4 const [data, setData] = useState({ hits: [] });
5
6 return (
7 8 {data.hits.map(item => ( 9 - 10 {item.title}a>11 li>12 ))}13 ul>
14 );
15 }
16
17 export default App;
App 组件显示了一个项目列表(hits=Hacker News 文章)。状态和状态更新函数来自useState 的 hook。他是来负责管理我们这个 data 的状态的。userState 中的第一个值是data 的初始值。其实就是个解构赋值。
这里我们使用 axios 来获取数据,当然,你也可以使用别的开源库。
1import React, { useState, useEffect } from 'react';
2import axios from 'axios';
3
4function App() {
5 const [data, setData] = useState({ hits: [] });
6
7 useEffect(async () => {
8 const result = await axios(
9 'https://hn.algolia.com/api/v1/search?query=redux',
10 );
11
12 setData(result.data);
13 });
14
15 return (
16 17 {data.hits.map(item => (18 - 19 {item.title}a>20 li>21 ))}22 ul>
23 );
24}
25
26export default App;
这里我们使用 useEffect 的 effect hook 来获取数据。并且使用 useState 中的 setData 来更新组件状态。
但是如上代码运行的时候,你会发现一个特别烦人的循环问题。effect hook 的触发不仅仅是在组件第一次加载的时候,还有在每一次更新的时候也会触发。由于我们在获取到数据后就进行设置了组件状态,然后又触发了 effect hook。所以就会出现死循环。很显然,这是一个 bug!我们只想在组件第一次加载的时候获取数据 ,这也就是为什么你可以提供一个空数组作为 useEffect
的第二个参数以避免在组件更新的时候也触发它。当然,这样的话,也就是在组件加载的时候触发。
1 import React, { useState, useEffect } from 'react';
2 import axios from 'axios';
3
4 function App() {
5 const [data, setData] = useState({ hits: [] });
6
7 useEffect(async () => {
8 const result = await axios(
9 'https://hn.algolia.com/api/v1/search?query=redux',
10 );
11
12 setData(result.data);
13 }, []);
14
15 return (
16 17 {data.hits.map(item => (18 - 19 {item.title}a>20 li>21 ))}22 ul>
23 );
24 }
25
26 export default App;
第二个参数可以用来定义 hook 所依赖的所有变量(在这个数组中),如果其中一个变量发生变化,则就会触发这个 hook 的运行。如果传递的是一个空数组,则仅仅在第一次加载的时候运行。
是不是感觉 ,干了
shouldComponentUpdate
的事情
这里还有一个陷阱。在这个代码里面,我们使用 async/await
去获取第三方的 API 的接口数据,根据文档,每一个 async
都会返回一个 promise:async
函数声明定义了一个异步函数,它返回一个 AsyncFunction 对象。异步函数是通过事件循环异步操作的函数,使用隐式的 Promise 返回结果然而,effect hook 不应该返回任何内容,或者清除功能。这也就是为啥你看到了
这个警告:
07:41:22.910 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
中使用 async
的原因。但是我们可以通过如下方法解决:
1 import React, { useState, useEffect } from 'react';
2 import axios from 'axios';
3
4 function App() {
5 const [data, setData] = useState({ hits: [] });
6
7 useEffect(() => {
8 const fetchData = async () => {
9 const result = await axios(
10 'https://hn.algolia.com/api/v1/search?query=redux',
11 );
12
13 setData(result.data);
14 };
15
16 fetchData();
17 }, []);
18
19 return (
20 21 {data.hits.map(item => (22 - 23 {item.title}a>24 li>25 ))}26 ul>
27 );
28 }
29
30 export default App;
如上就是通过 React hooks 来获取 API 数据。但是,如果你对错误处理、loading、如何触发从表单中获取数据或者如何实现可重用的数据获取的钩子。请继续阅读。
目前我们已经通过组件第一次加载的时候获取了接口数据。但是,如何能够通过输入的字段来告诉 api 接口我对那个主题感兴趣呢?(就是怎么给接口传数据。这里原文说的有点啰嗦(还有 redux 关键字来混淆视听),我直接上代码吧)…
1 ...
2
3 function App() {
4 const [data, setData] = useState({ hits: [] });
5 const [query, setQuery] = useState('redux');
6
7 useEffect(() => {
8 const fetchData = async () => {
9 const result = await axios(
10 `http://hn.algolia.com/api/v1/search?query=${query}`,
11 );
12
13 setData(result.data);
14 };
15
16 fetchData();
17 }, []);
18
19 return (
20 ...
21 );
22 }
23
24 export default App;
这里我跳过一段,原文实在说的太细了。
缺少一件:当你尝试输入字段键入内容的时候,他是不会再去触发请求的。因为你提供的是一个空数组作为useEffect
的第二个参数是一个空数组,所以effect hook 的触发不依赖任何变量,因此只在组件第一次加载的时候触发。所以这里我们希望当 query 这个字段一改变的时候就触发搜索
1...
2
3function App() {
4 const [data, setData] = useState({ hits: [] });
5 const [query, setQuery] = useState('redux');
6
7 useEffect(() => {
8 const fetchData = async () => {
9 const result = await axios(
10 `http://hn.algolia.com/api/v1/search?query=${query}`,
11 );
12
13 setData(result.data);
14 };
15
16 fetchData();
17 }, [query]);
18
19 return (
20 ...
21 );
22}
23
24export default App;
如上,我们只是把 query
作为第二个参数传递给了 effect hook,这样的话,每当 query 改变的时候就会触发搜索。但是,这样就会出现了另一个问题:每一次的query 的字段变动都会触发搜索。如何提供一个按钮来触发请求呢?
1function App() {
2 const [data, setData] = useState({ hits: [] });
3 const [query, setQuery] = useState('redux');
4 const [search, setSearch] = useState('redux');
5
6 useEffect(() => {
7 const fetchData = async () => {
8 const result = await axios(
9 `http://hn.algolia.com/api/v1/search?query=${search}`,
10 );
11
12 setData(result.data);
13 };
14
15 fetchData();
16 }, [search]);
17
18 return (
19 20 setQuery(event.target.value)}24 />25
搜索的状态设置为组件的初始化状态,组件加载的时候就要触发搜索,类似的查询和搜索状态易造成混淆,为什么不把实际的 URL 设置为状态而不是搜索状态呢?
1function App() {
2 const [data, setData] = useState({ hits: [] });
3 const [query, setQuery] = useState('redux');
4 const [url, setUrl] = useState(
5 'https://hn.algolia.com/api/v1/search?query=redux',
6 );
7
8 useEffect(() => {
9 const fetchData = async () => {
10 const result = await axios(url);
11
12 setData(result.data);
13 };
14
15 fetchData();
16 }, [url]);
17
18 return (
19 20 setQuery(event.target.value)}24 />25 28 setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)29 }30 >31 Search32 button>3334 35 {data.hits.map(item => (36 - 37 {item.title}a>38 li>39 ))}40 ul>41 Fragment>42 );43}44
这是一个使用 effect hook 来获取数据的一个例子,你可以决定 effect hook 所以依赖的状态。一旦你点击或者其他的什么操作 setState 了,那么 effect hook 就会运行。但是这个例子中,只有当你的 url 发生变化了,才会再次去获取数据。
这里让我们来给程序添加一个 loading(加载器),这里需要另一个 state
1import React, { Fragment, useState, useEffect } from 'react';
2import axios from 'axios';
3
4function App() {
5 const [data, setData] = useState({ hits: [] });
6 const [query, setQuery] = useState('redux');
7 const [url, setUrl] = useState(
8 'https://hn.algolia.com/api/v1/search?query=redux',
9 );
10 const [isLoading, setIsLoading] = useState(false);
11
12 useEffect(() => {
13 const fetchData = async () => {
14 setIsLoading(true);
15
16 const result = await axios(url);
17
18 setData(result.data);
19 setIsLoading(false);
20 };
21
22 fetchData();
23 }, [url]);
24
25 return (
26 27 setQuery(event.target.value)}31 />32 35 setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)36 }37 >38 Search39 button>4041 {isLoading ? (42 Loading ...div>43 ) : (44 45 {data.hits.map(item => (46 - 47 {item.title}a>48 li>49 ))}50 ul>51 )}52 Fragment>53 );54}5556export default App;57
代码比较简单,不解释了
使用 Effect Hook 添加错误处理(Error Handling with React Hooks)
如何在 Effect Hook 中做一些错误处理呢?错误仅仅是一个 state ,一旦程序出现了 error state,则组件需要去渲染一些feedback 给用户。当我们使用 async/await
的时候,我们可以使用try/catch
,如下:
1import React, { Fragment, useState, useEffect } from 'react';
2import axios from 'axios';
3
4function App() {
5 const [data, setData] = useState({ hits: [] });
6 const [query, setQuery] = useState('redux');
7 const [url, setUrl] = useState(
8 'https://hn.algolia.com/api/v1/search?query=redux',
9 );
10 const [isLoading, setIsLoading] = useState(false);
11 const [isError, setIsError] = useState(false);
12
13 useEffect(() => {
14 const fetchData = async () => {
15 setIsError(false);
16 setIsLoading(true);
17
18 try {
19 const result = await axios(url);
20
21 setData(result.data);
22 } catch (error) {
23 setIsError(true);
24 }
25
26 setIsLoading(false);
27 };
28
29 fetchData();
30 }, [url]);
31
32 return (
33 34 setQuery(event.target.value)}38 />39 42 setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)43 }44 >45 Search46 button>4748 {isError && Something went wrong ...div>}4950 {isLoading ? (51 Loading ...div>52 ) : (53 54 {data.hits.map(item => (55 - 56 {item.title}a>57 li>58 ))}59 ul>60 )}61 Fragment>62 );63}6465export default App;66
每一次 effect hook 运行的时候都需要重置一下 error state,这是非常有必要的。因为用户可能想再发生错误的时候想再次尝试一下。
说白了,界面给用户反馈更加的友好
使用 React 中 Form 表单获取数据(Fetching Data with Forms and React)
1function App() {
2 ...
3
4 return (
5 6