React hook 中的数据获取
相關(guān)說明:
對(duì)于hook相關(guān)詞不翻譯,感覺翻譯后怪怪的。
effect hook 效果鉤子,用于執(zhí)行一些副作用例如獲取數(shù)據(jù) 。
state hook 狀態(tài)鉤子。
使用----------- 和 ----------- 標(biāo)出代碼需要關(guān)注的地方。
渣翻譯如下:
在這個(gè)指南中,我想給你展示使用state和effect hook在React hooks中如何獲取數(shù)據(jù)。我們將使用著名的 Hacker News API從高科技世界中獲取受歡迎的文章。你也可以為獲取數(shù)據(jù)實(shí)現(xiàn)自定義獲取數(shù)據(jù)的hook,這個(gè)hook可以在你的應(yīng)用中任何地方重用,也可以作為一個(gè)獨(dú)立的node包發(fā)布到npm上。
如果關(guān)于react的新特性你什么都不知道,可以查看這篇文章introduction to React Hooks。如果你想查看怎么通過React Hooks獲取數(shù)據(jù)例子的完整項(xiàng)目,查看這個(gè)GitHub 倉庫。
如果你只是想在使用React Hook獲取數(shù)據(jù)前有一個(gè)準(zhǔn)備:npm install use-data-api 并且參照這個(gè)文檔。如果你采用了不要忘了小星星哦:-)。
**注:**在未來,React沒有計(jì)劃為獲取數(shù)據(jù)添加專門的Hooks。反而,Suspense將會(huì)負(fù)責(zé)這個(gè)功能。下面的預(yù)演是學(xué)習(xí)react中關(guān)于state和effect hooks一個(gè)比較好的方法。
使用React Hooks獲取數(shù)據(jù)
如果你不熟悉在React中獲取數(shù)據(jù),查看我的在react中獲取大量的數(shù)據(jù)這篇文章。這篇文章會(huì)引導(dǎo)你使用React Comopnent 類獲取數(shù)據(jù),怎么樣可以讓獲取數(shù)據(jù)的邏輯通過 Render Prop Components 和 Higher-Order Components重用,并且怎么處理重用加載出錯(cuò)和加載中的狀態(tài)。在這篇文章中,我想給你展示以上這些通過React Hooks在函數(shù)式組件中的做法。
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這個(gè)App組件展示了項(xiàng)目列表(hits 是 Hacker News 的文章)。這個(gè)state和跟新state的函數(shù)來自于狀態(tài)鉤子useState的調(diào)用,它的責(zé)任是管理本地我們將要為App組件獲取的數(shù)據(jù)數(shù)據(jù)的狀態(tài),初始狀態(tài)的數(shù)據(jù)是一個(gè)對(duì)象中的空列表。還沒有人為這個(gè)數(shù)據(jù)設(shè)置任何狀態(tài)。
我們將使用axios去獲取數(shù)據(jù),但是是使用其他獲取數(shù)據(jù)的庫還是使用瀏覽器原生的fetch API由你決定。如果你還沒有安裝axios,你可以在命令行輸入npm install axios。然后實(shí)現(xiàn)你自己的獲取數(shù)據(jù)的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從接口獲取數(shù)據(jù),并且通過狀態(tài)鉤子的更新函數(shù)設(shè)置數(shù)據(jù)到組件的本地狀態(tài)中。promise 通過 async/await中 被 resolve。
然而,當(dāng)你運(yùn)行你的應(yīng)用的的時(shí)候,你應(yīng)該會(huì)陷入一個(gè)令人討厭的循環(huán)。effect hook會(huì)在組件掛載的時(shí)候運(yùn)行但是也會(huì)在組件跟新的時(shí)候運(yùn)行。因?yàn)槲覀冊诿看潍@取數(shù)據(jù)之后設(shè)置狀態(tài),然后組件跟新然后effect hook再次運(yùn)行。組件將會(huì)一次又一次的獲取數(shù)據(jù)。這是一個(gè)需要避免的問題。我們只希望在組件掛載的時(shí)候獲取數(shù)據(jù)。
這就是為什么你需要提供一個(gè)空數(shù)組作為effect hook的第二個(gè)參數(shù)的原因,是為了阻止在組件更新的時(shí)候激活它,只在組件掛載的時(shí)候激活它。
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;第二個(gè)參數(shù)被用于定義鉤子依賴的所有變量(分配到這個(gè)數(shù)組中)。如果有一個(gè)變量改變,鉤子會(huì)再次運(yùn)行。如果數(shù)組中沒有變量,這個(gè)鉤子在組件更新的時(shí)候就不會(huì)運(yùn)行,因?yàn)樗鼪]有監(jiān)聽任何變量。
還有最后一個(gè)問題。在代碼中,我們使用async/await從第三方接口獲取數(shù)據(jù)。根據(jù)文檔表述每個(gè)使用async注釋的函數(shù)都會(huì)返回一個(gè)隱含的promise對(duì)象:async函數(shù)聲明定義一個(gè)異步函數(shù),返回一個(gè)異步函數(shù)對(duì)象。*An asynchronous function is a function which operates asynchronously via the event loop, *異步函數(shù)是一個(gè)操作通過事件循環(huán)操作異步的函數(shù),使用隱式的Promise作為結(jié)果返回”。However, an effect hook should return nothing or a clean up function.然而,一個(gè)effect hook不應(yīng)該返回值或者返回一個(gè)清除函數(shù)。(這是個(gè)啥,return nothing)這就是為什么在你的開發(fā)者日志里面能看見下面的警告: 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直接使用異步函數(shù)的原因。讓我們來修復(fù)它,通過異步函數(shù)取代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中獲取數(shù)據(jù)。但是如果你對(duì)錯(cuò)誤處理,加載狀態(tài),怎么從表單觸發(fā)數(shù)據(jù)獲取,怎么實(shí)現(xiàn)一個(gè)重用的數(shù)據(jù)獲取鉤子感興趣, 請(qǐng)繼續(xù)閱讀。
如何以編程的方式/手動(dòng)觸發(fā)鉤子
很好,我們將會(huì)在組件掛載的時(shí)候獲取一次數(shù)據(jù)。但是怎么使用輸入的字段去告訴接口我們感興趣的話題呢?“Redux“作為默認(rèn)的查詢。但是哪些話題是關(guān)于"React"的呢?讓我們實(shí)現(xiàn)一個(gè)輸入框去讓人能夠獲取Redux以外的其他信息。因此為輸入框引入一個(gè)新的狀態(tài)。
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;目前,每個(gè)狀態(tài)都是獨(dú)立的,但是現(xiàn)在你想結(jié)合他們只獲取通過輸入框輸入的查詢字段指定文章。通過下面的改變,組件應(yīng)該在掛載的時(shí)候通過查詢字段獲取一次所有文章。
...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;有一塊被遺漏了:當(dāng)你在輸入框中輸入內(nèi)容的時(shí)候,在組件掛在之后effect hook不會(huì)獲取其他數(shù)據(jù)。這是因?yàn)槟阌靡粋€(gè)空數(shù)組作為effect hook函數(shù)的第二個(gè)參數(shù)。這個(gè)副作用就沒有依賴的變量,所以它只在組掛載的時(shí)候觸發(fā)。然而,現(xiàn)在effect hook應(yīng)該依賴query。一旦query改變,就應(yīng)該再次請(qǐng)求數(shù)據(jù)。
...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;在你改變輸入框中的值的時(shí)候應(yīng)該獲取一次數(shù)據(jù)。但是它帶來了另一個(gè)問題:你在輸入框中輸入每一個(gè)字符都會(huì)觸發(fā)并執(zhí)行effect hook,然后執(zhí)行獲取其他數(shù)據(jù)。提供一個(gè)按鈕去觸發(fā)請(qǐng)求,手動(dòng)觸發(fā)鉤子怎么樣?
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>); }現(xiàn)在,讓effect hook依賴search狀態(tài)而不是根據(jù)輸入的每個(gè)內(nèi)容波動(dòng)的query狀態(tài),用戶點(diǎn)擊一次按鈕,新的search狀態(tài)就會(huì)被設(shè)置并且應(yīng)該手動(dòng)觸發(fā)一次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的初始狀態(tài)也應(yīng)該和query的初始狀態(tài)一樣,因?yàn)榻M件也會(huì)在掛載的時(shí)候獲取數(shù)據(jù),因此結(jié)果應(yīng)該和輸入的一致。然而,query的search狀態(tài)一樣讓人有點(diǎn)疑惑。為啥不把search的狀態(tài)換成真實(shí)的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隱式獲取數(shù)據(jù)的情況。你可以決定這個(gè)effect hook依賴哪個(gè)狀態(tài),一旦你在點(diǎn)擊的時(shí)候或者其他副作用設(shè)置這個(gè)狀態(tài),這個(gè)effect hook將會(huì)再次執(zhí)行。在這個(gè)案例中,如果URL狀態(tài)改變了,effect hook會(huì)再次執(zhí)行從接口中獲取數(shù)據(jù)。
React Hooks中的加載指示
讓我來介紹一個(gè)獲取數(shù)據(jù)的加載指示器。它就是另一個(gè)狀態(tài)鉤子(state hook)管理的狀態(tài)(state)。這個(gè)加載的標(biāo)志被用于在App組件中渲染一個(gè)加載中的指示器。
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;當(dāng)effect hook在組件掛在或者URL狀態(tài)改變的時(shí)候被調(diào)用去獲取數(shù)據(jù),這個(gè)加載狀態(tài)就會(huì)被設(shè)置為true。當(dāng)請(qǐng)求完成了,這個(gè)加載狀態(tài)就會(huì)再次被設(shè)置為false。
React Hooks的錯(cuò)誤處理
在React hook怎么處理獲取數(shù)據(jù)出錯(cuò)呢?這個(gè)錯(cuò)誤只是通過另一個(gè)狀態(tài)鉤子初始化的。當(dāng)這個(gè)狀態(tài)表示出錯(cuò)了,這個(gè) App組件可以給用戶一個(gè)反饋。當(dāng)使用 async/await,常用try/catch塊去處理錯(cuò)誤。你可以在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;這個(gè)錯(cuò)誤的狀態(tài)在鉤子每次執(zhí)行的時(shí)候都會(huì)被重置。這是很有用的,因?yàn)樵谑〉恼?qǐng)求之后,用戶回想再次嘗試,應(yīng)該重置錯(cuò)誤狀態(tài)。為了檢查出錯(cuò)的情況,你可以將URL更改為無效的內(nèi)容。然后查看錯(cuò)誤消息是否顯示。
通過React和表單的獲取數(shù)據(jù)
在表單中如何獲取數(shù)據(jù)?至今,我們只組合了input和按鈕。當(dāng)你引入了更多的輸入元素,你就會(huì)想要使用表單元素包裹他們。另外,一個(gè)表單可能通過鍵盤的回車鍵觸發(fā)按鈕觸發(fā)提交。
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>); }但是現(xiàn)在瀏覽器在你點(diǎn)擊提交按鈕的時(shí)候會(huì)刷新,因?yàn)檫@是一個(gè)瀏覽器提交表單的原生行為。為了阻止默認(rèn)行為,我們可以調(diào)用React事件對(duì)象的函數(shù)。就像你在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>); }現(xiàn)在點(diǎn)擊提交按鈕的時(shí)候?yàn)g覽器就不會(huì)再刷新了。它就像之前那樣工作,但是這次使用form替換了原生的輸入字段和按鈕的結(jié)合。你也可以在鍵盤上按回車鍵提交表單。
自定義獲取數(shù)據(jù)鉤子
為了提取一個(gè)自定義獲取數(shù)據(jù)的鉤子,移動(dòng)每個(gè)屬于數(shù)據(jù)獲取數(shù)據(jù)的代碼到自己的函數(shù),除了屬于輸入字段的query狀態(tài),但是包含加載指示器和錯(cuò)誤處理。也要確定你在函數(shù)中返回了所有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]; } // -------------------------------------------------現(xiàn)在,你的新鉤子在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>); }初始狀態(tài)也可以通用,通過它簡化新的自定義鉤子。
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;通過自定義鉤子獲取數(shù)據(jù)。這個(gè)鉤子自己不知道關(guān)于接口的任何信息。他接受所有從外面?zhèn)魅氲膮?shù)并且只管理必要的狀態(tài),例如data,加載狀態(tài)和錯(cuò)誤狀態(tài)。他執(zhí)行請(qǐng)求和返回?cái)?shù)據(jù)給把它當(dāng)做自定義獲取數(shù)據(jù)鉤子使用的組件。
使用Reducer Hook獲取數(shù)據(jù)
至今,我們使用各個(gè)state hooks 去管理我們數(shù)據(jù)的數(shù)據(jù)獲取狀態(tài),加載和錯(cuò)誤狀態(tài)。然而,不知為啥,這些狀態(tài)被自己的state hook管理,它們應(yīng)該屬于一起的因?yàn)樗鼈冴P(guān)心相同的原因。就像你看到的一樣它們都在數(shù)據(jù)獲取函數(shù)中使用。它們是一起的一個(gè)很好的標(biāo)志是它們一個(gè)接著一個(gè)的使用(e.g setIsError,setIsLoading)。讓我們使用Reducer Hook結(jié)合并替換它們。
一個(gè)Reducer Hook 使用一個(gè)state對(duì)象和一個(gè)函數(shù)生成一個(gè)state對(duì)象。這個(gè)函數(shù)被稱為 —— dispatch函數(shù) —— 分發(fā)一個(gè)action,這個(gè)action里面有一個(gè)type屬性和一個(gè)可選的payload對(duì)象。所有這些信息在真實(shí)的reducer函數(shù)中被使用去從之前的狀態(tài)生成一個(gè)新的狀態(tài),所有信息表示為這個(gè)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,});// -------------------------------------------------... };這個(gè)Reducer Hook使用reducer函數(shù)和初始的狀態(tài)對(duì)象作為參數(shù)。在我們的例子里,data的初始化狀態(tài),loading和error的初始狀態(tài)沒有改變,但是它們替換了單個(gè)state hooks,通過reducer hook匯總到一個(gè)state對(duì)象里面管理。
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]);... };現(xiàn)在,獲取數(shù)據(jù)的時(shí)候可以使用dispatch函數(shù)發(fā)送一個(gè)信息給reducer函數(shù)。dispatch分發(fā)的對(duì)象有一個(gè)約定的type屬性和一個(gè)可選的payload屬性。這個(gè)type告訴reducer函數(shù)哪個(gè)狀態(tài)需要改變和reducer可以使用payload去提取一個(gè)新的state。畢竟我們只有三個(gè)狀態(tài)改變:初始的獲取進(jìn)程。通知成功的數(shù)據(jù)獲取結(jié)果。通知失敗的數(shù)據(jù)獲取結(jié)果。
在自定義鉤子的最后,這個(gè)state就像之前一樣被返回出去,但是因?yàn)槲覀円徽麄€(gè)state對(duì)象,所以再也沒有獨(dú)立的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函數(shù)的實(shí)現(xiàn)。它需要發(fā)出三個(gè)不同的狀態(tài)轉(zhuǎn)換,叫作 FETCH_INIT, FETCH_SUCCESS 和 FETCH_FAILURE。每個(gè)狀態(tài)轉(zhuǎn)換需要返回一個(gè)新的狀態(tài)對(duì)象。讓我們看看這個(gè)怎么通過switch case實(shí)現(xiàn):
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();}// ------------------------------------------------- };一個(gè)reducer函數(shù)可以通過它的arguments訪問當(dāng)前的state和action。現(xiàn)在switch case語句每個(gè)狀態(tài)被轉(zhuǎn)換只返回之前的state。使用解構(gòu)語句去保證state對(duì)象不可變 - 意味著state是不能直接改變的 - 這是最佳實(shí)踐。現(xiàn)在讓我們覆蓋一些當(dāng)前的state需要被返回的屬性來改變狀態(tài)轉(zhuǎn)換的狀態(tài):
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();} };現(xiàn)在每個(gè)state的轉(zhuǎn)換。都是通過action的type決定的,基于上一個(gè)state和可選的payload屬性返回一個(gè)新的state。例如,在請(qǐng)求成功的案例里,payload被用于設(shè)置新state對(duì)象的data。
總之,Reducer Hook確保狀態(tài)管理的這一部分用自己的邏輯封裝。通過提供action type和可選的payloads,你將總是可以預(yù)測變化。另外,你將不會(huì)再非法的state下運(yùn)行。例如,先前可能搞錯(cuò)了設(shè)置isLoading和isError狀態(tài)變成true。我們在這種情況下怎么展示呢?現(xiàn)在每個(gè)狀態(tài)的改變都被reducer 函數(shù)變成一個(gè)合法的state對(duì)象。
在Effect Hook中阻止數(shù)據(jù)獲取
在React中設(shè)置未掛載組件的狀態(tài)是一個(gè)常見的問題(e.g. 由于通過React Router導(dǎo)航的)。我之前寫過關(guān)于這個(gè)問題的文章,它描述了在各種場景中如何阻止在未掛載的組件中設(shè)置state。讓我們看看怎么在我們自定義的數(shù)據(jù)獲取鉤子里面阻止?fàn)顟B(tài)設(shè)置。
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]; };每個(gè)Effect Hook都有一個(gè)匹配的清除函數(shù),它會(huì)在組件卸載的時(shí)候執(zhí)行。這個(gè)清除函數(shù)是一個(gè)從hook中返回的函數(shù)。在我們的例子里,我們使用名字為didCancel的boolean類型的標(biāo)志讓我們數(shù)據(jù)獲取邏輯知道組件狀態(tài)(掛載的/未掛載的)。如果組件完成卸載,這個(gè)標(biāo)志應(yīng)該設(shè)置為true這個(gè)結(jié)果會(huì)阻止異步獲取數(shù)據(jù)完成之后設(shè)置組件的狀態(tài) 。
注:其實(shí)數(shù)據(jù)請(qǐng)求沒有被終止 — 可以通過Axios Cancellation實(shí)現(xiàn)終止請(qǐng)求的功能—但是狀態(tài)遷移在組件卸載后不會(huì)再執(zhí)行。由于Axios Concellation在我看來沒有更好的API,這個(gè)boolean值的標(biāo)志也可以完成阻止設(shè)置state的工作。
你已經(jīng)學(xué)會(huì)怎么在React獲取數(shù)據(jù)的時(shí)候使用React hooks中的state和effets鉤子。
如果你對(duì)在React類組件(函數(shù)式組件)里面使用render屬性和高階組件獲取數(shù)據(jù)感到好奇,查看本篇文章開始處我的其他文章。除此以外,我希望這篇文章有助于你學(xué)習(xí)React Hooks和在真實(shí)世界中使用他們。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的React hook 中的数据获取的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: create react app创建的项
- 下一篇: 在React中获取数据