相关说明:
-
对于hook相关词不翻译,感觉翻译后怪怪的。
-
effect hook 效果钩子,用于执行一些副作用例如获取数据 。
-
state hook 状态钩子。
-
使用----------- 和 ----------- 标出代码需要关注的地方。
渣翻译如下:
在这个指南中,我想给你展示使用state和effect hook在React hooks中如何获取数据。我们将使用著名的 Hacker News API从高科技世界中获取受欢迎的文章。你也可以为获取数据实现自定义获取数据的hook,这个hook可以在你的应用中任何地方重用,也可以作为一个独立的node包发布到npm上。
如果关于react的新特性你什么都不知道,可以查看这篇文章introduction to React Hooks。如果你想查看怎么通过React Hooks获取数据例子的完整项目,查看这个GitHub 仓库。
如果你只是想在使用React Hook获取数据前有一个准备:npm install use-data-api
并且参照这个文档。如果你采用了不要忘了小星星哦:-)。
**注:**在未来,React没有计划为获取数据添加专门的Hooks。反而,Suspense将会负责这个功能。下面的预演是学习react中关于state和effect hooks一个比较好的方法。
使用React Hooks获取数据
如果你不熟悉在React中获取数据,查看我的在react中获取大量的数据这篇文章。这篇文章会引导你使用React Comopnent 类获取数据,怎么样可以让获取数据的逻辑通过 Render Prop Components 和 Higher-Order Components重用,并且怎么处理重用加载出错和加载中的状态。在这篇文章中,我想给你展示以上这些通过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组件展示了项目列表(hits 是 Hacker News 的文章)。这个state和跟新state的函数来自于状态钩子useState
的调用,它的责任是管理本地我们将要为App组件获取的数据数据的状态,初始状态的数据是一个对象中的空列表。还没有人为这个数据设置任何状态。
我们将使用axios去获取数据,但是是使用其他获取数据的库还是使用浏览器原生的fetch API由你决定。如果你还没有安装axios,你可以在命令行输入npm install axios
。然后实现你自己的获取数据的effect hook。
// -------------------------------------------------
import React, { useState, useEffect } from 'react';
import axios from 'axios';
// -------------------------------------------------function App() {const [data, setData] = useState({ hits: [] });// -------------------------------------------------useEffect(async () => {const result = await axios('http://hn.algolia.com/api/v1/search?query=redux',);setData(result.data);});// -------------------------------------------------return (<ul>{data.hits.map(item => (<li key={item.objectID}><a href={item.url}>{item.title}</a></li>))}</ul>);
}export default App;
名为useEffect的effect hook被用于使用axios从接口获取数据,并且通过状态钩子的更新函数设置数据到组件的本地状态中。promise 通过 async/await中 被 resolve。
然而,当你运行你的应用的的时候,你应该会陷入一个令人讨厌的循环。effect hook会在组件挂载的时候运行但是也会在组件跟新的时候运行。因为我们在每次获取数据之后设置状态,然后组件跟新然后effect hook再次运行。组件将会一次又一次的获取数据。这是一个需要避免的问题。我们只希望在组件挂载的时候获取数据。
这就是为什么你需要提供一个空数组作为effect hook的第二个参数的原因,是为了阻止在组件更新的时候激活它,只在组件挂载的时候激活它。
import React, { useState, useEffect } from 'react';
import axios from 'axios';function App() {const [data, setData] = useState({ hits: [] });useEffect(async () => {const result = await axios('http://hn.algolia.com/api/v1/search?query=redux',);setData(result.data);// -------------------------------------------------}, []);// -------------------------------------------------return (<ul>{data.hits.map(item => (<li key={item.objectID}><a href={item.url}>{item.title}</a></li>))}</ul>);
}export default App;
第二个参数被用于定义钩子依赖的所有变量(分配到这个数组中)。如果有一个变量改变,钩子会再次运行。如果数组中没有变量,这个钩子在组件更新的时候就不会运行,因为它没有监听任何变量。
还有最后一个问题。在代码中,我们使用async/await从第三方接口获取数据。根据文档表述每个使用async注释的函数都会返回一个隐含的promise对象:async函数声明定义一个异步函数,返回一个异步函数对象。*An asynchronous function is a function which operates asynchronously via the event loop, *异步函数是一个操作通过事件循环操作异步的函数,使用隐式的Promise作为结果返回”。However, an effect hook should return nothing or a clean up function.然而,一个effect hook不应该返回值或者返回一个清除函数。(这是个啥,return nothing)这就是为什么在你的开发者日志里面能看见下面的警告: 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
直接使用异步函数的原因。让我们来修复它,通过异步函数取代effect hook。
import React, { useState, useEffect } from 'react';
import axios from 'axios';function App() {const [data, setData] = useState({ hits: [] });useEffect(() => {// -------------------------------------------------const fetchData = async () => {const result = await axios('http://hn.algolia.com/api/v1/search?query=redux',);setData(result.data);};fetchData();// -------------------------------------------------}, []);return (<ul>{data.hits.map(item => (<li key={item.objectID}><a href={item.url}>{item.title}</a></li>))}</ul>);
}export default App;
简而言之这就是在React hooks中获取数据。但是如果你对错误处理,加载状态,怎么从表单触发数据获取,怎么实现一个重用的数据获取钩子感兴趣, 请继续阅读。
如何以编程的方式/手动触发钩子
很好,我们将会在组件挂载的时候获取一次数据。但是怎么使用输入的字段去告诉接口我们感兴趣的话题呢?“Redux“作为默认的查询。但是哪些话题是关于"React"的呢?让我们实现一个输入框去让人能够获取Redux以外的其他信息。因此为输入框引入一个新的状态。
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';function App() {const [data, setData] = useState({ hits: [] });// -------------------------------------------------const [query, setQuery] = useState('redux');// -------------------------------------------------useEffect(() => {const fetchData = async () => {const result = await axios('http://hn.algolia.com/api/v1/search?query=redux',);setData(result.data);};fetchData();}, []);return (<Fragment>{/* ------------------------------------------------- */}<inputtype="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></Fragment>);
}export default App;
目前,每个状态都是独立的,但是现在你想结合他们只获取通过输入框输入的查询字段指定文章。通过下面的改变,组件应该在挂载的时候通过查询字段获取一次所有文章。
...function App() {const [data, setData] = useState({ hits: [] });const [query, setQuery] = useState('redux');useEffect(() => {const fetchData = async () => {const result = await axios(// -------------------------------------------------`http://hn.algolia.com/api/v1/search?query=${query}`,// -------------------------------------------------);setData(result.data);};fetchData();}, []);return (...);
}export default App;
有一块被遗漏了:当你在输入框中输入内容的时候,在组件挂在之后effect hook不会获取其他数据。这是因为你用一个空数组作为effect hook函数的第二个参数。这个副作用就没有依赖的变量,所以它只在组挂载的时候触发。然而,现在effect hook应该依赖query。一旦query改变,就应该再次请求数据。
...function App() {const [data, setData] = useState({ hits: [] });const [query, setQuery] = useState('redux');useEffect(() => {const fetchData = async () => {const result = await axios(`http://hn.algolia.com/api/v1/search?query=${query}`,);setData(result.data);};fetchData();// -------------------------------------------------}, [query]);// -------------------------------------------------return (...);
}export default App;
在你改变输入框中的值的时候应该获取一次数据。但是它带来了另一个问题:你在输入框中输入每一个字符都会触发并执行effect hook,然后执行获取其他数据。提供一个按钮去触发请求,手动触发钩子怎么样?
function App() {const [data, setData] = useState({ hits: [] });const [query, setQuery] = useState('redux');// -------------------------------------------------const [search, setSearch] = useState('');// -------------------------------------------------useEffect(() => {const fetchData = async () => {const result = await axios(`http://hn.algolia.com/api/v1/search?query=${query}`,);setData(result.data);};fetchData();}, [query]);return (<Fragment><inputtype="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></Fragment>);
}
现在,让effect hook依赖search状态而不是根据输入的每个内容波动的query状态,用户点击一次按钮,新的search状态就会被设置并且应该手动触发一次effect hook。
...function App() {const [data, setData] = useState({ hits: [] });const [query, setQuery] = useState('redux');// -------------------------------------------------const [search, setSearch] = useState('redux');// -------------------------------------------------useEffect(() => {const fetchData = async () => {const result = await axios(// -------------------------------------------------`http://hn.algolia.com/api/v1/search?query=${search}`,// -------------------------------------------------);setData(result.data);};fetchData();// -------------------------------------------------}, [search]);// -------------------------------------------------return (...);
}export default App;
search的初始状态也应该和query的初始状态一样,因为组件也会在挂载的时候获取数据,因此结果应该和输入的一致。然而,query的search状态一样让人有点疑惑。为啥不把search的状态换成真实的URL呢?
function App() {const [data, setData] = useState({ hits: [] });const [query, setQuery] = useState('redux');// -------------------------------------------------const [url, setUrl] = useState('http://hn.algolia.com/api/v1/search?query=redux',);// -------------------------------------------------useEffect(() => {const fetchData = async () => {// -------------------------------------------------const result = await axios(url);// -------------------------------------------------setData(result.data);};fetchData();// -------------------------------------------------}, [url]);// -------------------------------------------------return (<Fragment><inputtype="text"value={query}{/* ------------------------------------------------- */}onChange={event => setQuery(event.target.value)}{/* ------------------------------------------------- */}/><buttontype="button"onClick={() =>setUrl(`http://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></Fragment>);
}
这就是使用effect hook隐式获取数据的情况。你可以决定这个effect hook依赖哪个状态,一旦你在点击的时候或者其他副作用设置这个状态,这个effect hook将会再次执行。在这个案例中,如果URL状态改变了,effect hook会再次执行从接口中获取数据。
React Hooks中的加载指示
让我来介绍一个获取数据的加载指示器。它就是另一个状态钩子(state hook)管理的状态(state)。这个加载的标志被用于在App组件中渲染一个加载中的指示器。
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';function App() {const [data, setData] = useState({ hits: [] });const [query, setQuery] = useState('redux');const [url, setUrl] = useState('http://hn.algolia.com/api/v1/search?query=redux',);// -------------------------------------------------const [isLoading, setIsLoading] = useState(false);// -------------------------------------------------useEffect(() => {const fetchData = async () => {// -------------------------------------------------setIsLoading(true);// -------------------------------------------------const result = await axios(url);setData(result.data);// -------------------------------------------------setIsLoading(false);// -------------------------------------------------};fetchData();}, [url]);return (<Fragment><inputtype="text"value={query}onChange={event => setQuery(event.target.value)}/><buttontype="button"onClick={() =>setUrl(`http://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>{/* ------------------------------------------------- */})}{/* ------------------------------------------------- */}</Fragment>);
}export default App;
当effect hook在组件挂在或者URL状态改变的时候被调用去获取数据,这个加载状态就会被设置为true。当请求完成了,这个加载状态就会再次被设置为false。
React Hooks的错误处理
在React hook怎么处理获取数据出错呢?这个错误只是通过另一个状态钩子初始化的。当这个状态表示出错了,这个 App组件可以给用户一个反馈。当使用 async/await,常用try/catch块去处理错误。你可以在effect hook里面这样做:
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';function App() {const [data, setData] = useState({ hits: [] });const [query, setQuery] = useState('redux');const [url, setUrl] = useState('http://hn.algolia.com/api/v1/search?query=redux',);const [isLoading, setIsLoading] = useState(false);// -------------------------------------------------const [isError, setIsError] = useState(false);// -------------------------------------------------useEffect(() => {const fetchData = async () => {// -------------------------------------------------setIsError(false);// -------------------------------------------------setIsLoading(true);// -------------------------------------------------try {// -------------------------------------------------const result = await axios(url);setData(result.data);// -------------------------------------------------} catch (error) {setIsError(true);}// -------------------------------------------------setIsLoading(false);};fetchData();}, [url]);return (<Fragment><inputtype="text"value={query}onChange={event => setQuery(event.target.value)}/><buttontype="button"onClick={() =>setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)}>Search</button>{/* ------------------------------------------------- */}{isError && <div>Something went wrong ...</div>}{/* ------------------------------------------------- */}{isLoading ? (<div>Loading ...</div>) : (<ul>{data.hits.map(item => (<li key={item.objectID}><a href={item.url}>{item.title}</a></li>))}</ul>)}</Fragment>);
}export default App;
这个错误的状态在钩子每次执行的时候都会被重置。这是很有用的,因为在失败的请求之后,用户回想再次尝试,应该重置错误状态。为了检查出错的情况,你可以将URL更改为无效的内容。然后查看错误消息是否显示。
通过React和表单的获取数据
在表单中如何获取数据?至今,我们只组合了input和按钮。当你引入了更多的输入元素,你就会想要使用表单元素包裹他们。另外,一个表单可能通过键盘的回车键触发按钮触发提交。
function App() {...return (<Fragment>{/* ------------------------------------------------- */}<formonSubmit={() =>setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)}>{/* ------------------------------------------------- */}<inputtype="text"value={query}onChange={event => setQuery(event.target.value)}/>{/* ------------------------------------------------- */}<button type="submit">Search</button></form>{/* ------------------------------------------------- */}{isError && <div>Something went wrong ...</div>}...</Fragment>);
}
但是现在浏览器在你点击提交按钮的时候会刷新,因为这是一个浏览器提交表单的原生行为。为了阻止默认行为,我们可以调用React事件对象的函数。就像你在React类组件中做的那样。
function App() {...return (<Fragment>{/* ------------------------------------------------- */}<form onSubmit={event => {{/* ------------------------------------------------- */}setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);{/* ------------------------------------------------- */}event.preventDefault();}}>{/* ------------------------------------------------- */}<inputtype="text"value={query}onChange={event => setQuery(event.target.value)}/><button type="submit">Search</button></form>{isError && <div>Something went wrong ...</div>}...</Fragment>);
}
现在点击提交按钮的时候浏览器就不会再刷新了。它就像之前那样工作,但是这次使用form替换了原生的输入字段和按钮的结合。你也可以在键盘上按回车键提交表单。
自定义获取数据钩子
为了提取一个自定义获取数据的钩子,移动每个属于数据获取数据的代码到自己的函数,除了属于输入字段的query状态,但是包含加载指示器和错误处理。也要确定你在函数中返回了所有App组件里必要的变量。
// -------------------------------------------------
const useHackerNewsApi = () => {
// -------------------------------------------------const [data, setData] = useState({ hits: [] });const [url, setUrl] = useState('http://hn.algolia.com/api/v1/search?query=redux',);const [isLoading, setIsLoading] = useState(false);const [isError, setIsError] = useState(false);useEffect(() => {const fetchData = async () => {setIsError(false);setIsLoading(true);try {const result = await axios(url);setData(result.data);} catch (error) {setIsError(true);}setIsLoading(false);};fetchData();}, [url]);
// -------------------------------------------------return [{ data, isLoading, isError }, setUrl];
}
// -------------------------------------------------
现在,你的新钩子在App组件中又可以使用了。
function App() {const [query, setQuery] = useState('redux');// -------------------------------------------------const [{ data, isLoading, isError }, doFetch] = useHackerNewsApi();// -------------------------------------------------return (<Fragment><form onSubmit={event => {{/* ------------------------------------------------- */}doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`);{/* ------------------------------------------------- */}event.preventDefault();}}><inputtype="text"value={query}onChange={event => setQuery(event.target.value)}/><button type="submit">Search</button></form>...</Fragment>);
}
初始状态也可以通用,通过它简化新的自定义钩子。
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';// -------------------------------------------------
const useDataApi = (initialUrl, initialData) => {const [data, setData] = useState(initialData);const [url, setUrl] = useState(initialUrl);
// -------------------------------------------------const [isLoading, setIsLoading] = useState(false);const [isError, setIsError] = useState(false);useEffect(() => {const fetchData = async () => {setIsError(false);setIsLoading(true);try {const result = await axios(url);setData(result.data);} catch (error) {setIsError(true);}setIsLoading(false);};fetchData();}, [url]);return [{ data, isLoading, isError }, setUrl];
};function App() {const [query, setQuery] = useState('redux');// -------------------------------------------------const [{ data, isLoading, isError }, doFetch] = useDataApi('http://hn.algolia.com/api/v1/search?query=redux',{ hits: [] },);// -------------------------------------------------return (<Fragment><formonSubmit={event => {doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`,);event.preventDefault();}}><inputtype="text"value={query}onChange={event => setQuery(event.target.value)}/><button type="submit">Search</button></form>{isError && <div>Something went wrong ...</div>}{isLoading ? (<div>Loading ...</div>) : (<ul>{data.hits.map(item => (<li key={item.objectID}><a href={item.url}>{item.title}</a></li>))}</ul>)}</Fragment>);
}export default App;
通过自定义钩子获取数据。这个钩子自己不知道关于接口的任何信息。他接受所有从外面传入的参数并且只管理必要的状态,例如data,加载状态和错误状态。他执行请求和返回数据给把它当做自定义获取数据钩子使用的组件。
使用Reducer Hook获取数据
至今,我们使用各个state hooks 去管理我们数据的数据获取状态,加载和错误状态。然而,不知为啥,这些状态被自己的state hook管理,它们应该属于一起的因为它们关心相同的原因。就像你看到的一样它们都在数据获取函数中使用。它们是一起的一个很好的标志是它们一个接着一个的使用(e.g setIsError
,setIsLoading
)。让我们使用Reducer Hook结合并替换它们。
一个Reducer Hook 使用一个state对象和一个函数生成一个state对象。这个函数被称为 —— dispatch函数 —— 分发一个action,这个action里面有一个type属性和一个可选的payload对象。所有这些信息在真实的reducer函数中被使用去从之前的状态生成一个新的状态,所有信息表示为这个action的payload和type。让我们看看在代码中是怎么工作的。
import React, {Fragment,useState,useEffect,// -------------------------------------------------useReducer,// -------------------------------------------------
} from 'react';
import axios from 'axios';// -------------------------------------------------
const dataFetchReducer = (state, action) => {...
};
// -------------------------------------------------const useDataApi = (initialUrl, initialData) => {const [url, setUrl] = useState(initialUrl);// -------------------------------------------------const [state, dispatch] = useReducer(dataFetchReducer, {isLoading: false,isError: false,data: initialData,});// -------------------------------------------------...
};
这个Reducer Hook使用reducer函数和初始的状态对象作为参数。在我们的例子里,data的初始化状态,loading和error的初始状态没有改变,但是它们替换了单个state hooks,通过reducer hook汇总到一个state对象里面管理。
const dataFetchReducer = (state, action) => {...
};const useDataApi = (initialUrl, initialData) => {const [url, setUrl] = useState(initialUrl);const [state, dispatch] = useReducer(dataFetchReducer, {isLoading: false,isError: false,data: initialData,});useEffect(() => {const fetchData = async () => {// -------------------------------------------------dispatch({ type: 'FETCH_INIT' });// -------------------------------------------------try {const result = await axios(url);// -------------------------------------------------dispatch({ type: 'FETCH_SUCCESS', payload: result.data });// -------------------------------------------------} catch (error) {// -------------------------------------------------dispatch({ type: 'FETCH_FAILURE' });// -------------------------------------------------}};fetchData();}, [url]);...
};
现在,获取数据的时候可以使用dispatch函数发送一个信息给reducer函数。dispatch分发的对象有一个约定的type属性和一个可选的payload属性。这个type告诉reducer函数哪个状态需要改变和reducer可以使用payload去提取一个新的state。毕竟我们只有三个状态改变:初始的获取进程。通知成功的数据获取结果。通知失败的数据获取结果。
在自定义钩子的最后,这个state就像之前一样被返回出去,但是因为我们一整个state对象,所以再也没有独立的state了。这样,使用useDataApi
自定义钩子的人还可以访问到 data
,isLoading
和 isError
。
const useDataApi = (initialUrl, initialData) => {const [url, setUrl] = useState(initialUrl);const [state, dispatch] = useReducer(dataFetchReducer, {isLoading: false,isError: false,data: initialData,});...// -------------------------------------------------return [state, setUrl];// -------------------------------------------------
};
最后但是很重要的是,缺少reducer函数的实现。它需要发出三个不同的状态转换,叫作 FETCH_INIT
, FETCH_SUCCESS
和 FETCH_FAILURE
。每个状态转换需要返回一个新的状态对象。让我们看看这个怎么通过switch case实现:
const dataFetchReducer = (state, action) => {// -------------------------------------------------switch (action.type) {case 'FETCH_INIT':return { ...state };case 'FETCH_SUCCESS':return { ...state };case 'FETCH_FAILURE':return { ...state };default:throw new Error();}// -------------------------------------------------
};
一个reducer函数可以通过它的arguments访问当前的state和action。现在switch case语句每个状态被转换只返回之前的state。使用解构语句去保证state对象不可变 - 意味着state是不能直接改变的 - 这是最佳实践。现在让我们覆盖一些当前的state需要被返回的属性来改变状态转换的状态:
const dataFetchReducer = (state, action) => {switch (action.type) {case 'FETCH_INIT':return {...state,// -------------------------------------------------isLoading: true,isError: false// -------------------------------------------------};case 'FETCH_SUCCESS':return {...state,// -------------------------------------------------isLoading: false,isError: false,data: action.payload,// -------------------------------------------------};case 'FETCH_FAILURE':return {...state,// -------------------------------------------------isLoading: false,isError: true,// -------------------------------------------------};default:throw new Error();}
};
现在每个state的转换。都是通过action的type决定的,基于上一个state和可选的payload属性返回一个新的state。例如,在请求成功的案例里,payload被用于设置新state对象的data。
总之,Reducer Hook确保状态管理的这一部分用自己的逻辑封装。通过提供action type和可选的payloads,你将总是可以预测变化。另外,你将不会再非法的state下运行。例如,先前可能搞错了设置isLoading
和isError
状态变成true。我们在这种情况下怎么展示呢?现在每个状态的改变都被reducer 函数变成一个合法的state对象。
在Effect Hook中阻止数据获取
在React中设置未挂载组件的状态是一个常见的问题(e.g. 由于通过React Router导航的)。我之前写过关于这个问题的文章,它描述了在各种场景中如何阻止在未挂载的组件中设置state。让我们看看怎么在我们自定义的数据获取钩子里面阻止状态设置。
const useDataApi = (initialUrl, initialData) => {const [url, setUrl] = useState(initialUrl);const [state, dispatch] = useReducer(dataFetchReducer, {isLoading: false,isError: false,data: initialData,});useEffect(() => {// -------------------------------------------------let didCancel = false;// -------------------------------------------------const fetchData = async () => {dispatch({ type: 'FETCH_INIT' });try {const result = await axios(url);// -------------------------------------------------if (!didCancel) {// -------------------------------------------------dispatch({ type: 'FETCH_SUCCESS', payload: result.data });// -------------------------------------------------}// -------------------------------------------------} catch (error) {// -------------------------------------------------if (!didCancel) {// -------------------------------------------------dispatch({ type: 'FETCH_FAILURE' });// -------------------------------------------------}// -------------------------------------------------}};fetchData();// -------------------------------------------------return () => {didCancel = true;};// -------------------------------------------------}, [url]);return [state, setUrl];
};
每个Effect Hook都有一个匹配的清除函数,它会在组件卸载的时候执行。这个清除函数是一个从hook中返回的函数。在我们的例子里,我们使用名字为didCancel
的boolean类型的标志让我们数据获取逻辑知道组件状态(挂载的/未挂载的)。如果组件完成卸载,这个标志应该设置为true
这个结果会阻止异步获取数据完成之后设置组件的状态 。
注:其实数据请求没有被终止 — 可以通过Axios Cancellation实现终止请求的功能—但是状态迁移在组件卸载后不会再执行。由于Axios Concellation在我看来没有更好的API,这个boolean值的标志也可以完成阻止设置state的工作。
你已经学会怎么在React获取数据的时候使用React hooks中的state和effets钩子。
如果你对在React类组件(函数式组件)里面使用render属性和高阶组件获取数据感到好奇,查看本篇文章开始处我的其他文章。除此以外,我希望这篇文章有助于你学习React Hooks和在真实世界中使用他们。