摘要:在這種情況下,如果狀態發生變化,將再次運行以從獲取數據。你可以在內做到在表單中獲取數據到目前為止,我們只有和按鈕的組合。現在,在獲取數據時,可以使用向函數發送信息。例如,在成功請求的情況下,用于設置新狀態對象的數據。
原文鏈接: https://www.robinwieruch.de/r...
在本教程中,我想通過state和effect hook來像你展示如何用React Hooks來獲取數據。我將會使用Hacker News的API來獲取熱門的技術文章。你將會實現一個屬于你自己的自定義hook來在你程序的任何地方復用,或者是作為一個npm包發布出來。
如果你還不知道這個React的新特性,那么點擊React Hooks介紹,如果你想直接查看最后的實現效果,請點擊這個github倉庫。
注意:在未來,React Hooks將不會用于React的數據獲取,一個叫做Suspense的特性將會去負責它。但下面的教程仍會讓你去更多的了解關于React中的state和effect hook。
用React Hooks去獲取數據如果你對在React中獲取數據還不熟悉,可以查看我其他的React獲取數據的文章。它將會引導你通過使用React的class組件來獲取數據,并且還可以和render props或者高階組件一起使用,以及結合錯誤處理和加載狀態。在這篇文章中,我將會在function組件中使用React Hooks來展示這些功能。
import React, { useState } from "react"; function App() { const [data, setData] = useState({ hits: [] }); return (
這個App組件展示了一個包含很多項的list(hits = Hacker News 文章)。state和state的更新函數來自于state hook中useState的調用,它負責管理我們用來渲染list數據的本地狀態,初始狀態是一個空數組,此時還沒有為其設置任何的狀態。
我們將使用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 (
通過axios在useEffect中獲取數據,然后通過setData將數據放到組件本地的state中,并通過async/await來處理Promise。
然而當你運行程序的時候,你應該會遇到一個討厭的循環。effect hook不僅在組件mount的時候也會在update的時候運行。因為我們在每一次的數據獲取之后,會去通過setState設置狀態,這時候組件update然后effect就會運行一遍,這就造成了數據一次又一次的獲取。我們僅僅是想要在組件mount的時候來獲取一次數據,這就是為什么我們需要在useEffect的第二個參數提供一個空數組,從而實現只在mount的時候觸發數據獲取而不是每一次update。
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 (
第二個參數可以定義hooks所依賴的變量(在一個數組中去分配),如果一個變量改變了,hooks將會執行一次,如果是一個空數組的話,hooks將不會在組件更新的時候執行,因為它沒有監聽到任何的變量。
這里還有一個陷阱,在代碼中,我們使用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的原因。讓我們通過在effect內部使用異步函數來實現它的解決方案。
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 (
簡而言之,這就是用React Hooks獲取數據。但是,如果你對錯誤處理、加載提示、如何從表單中觸發數據獲取以及如何實現可重用的數據獲取hook感興趣,請繼續閱讀。
如何通過編程方式/手動方式觸發hook?好的,我們在mount后獲取了一次數據,但是,如果使用input的字段來告訴API哪一個話題是我們感興趣的呢?“Redux”可以作為我們的默認查詢,如果是關于“React”的呢?讓我們實現一個input元素,使某人能夠獲取“Redux”以外的話題。因此,為input元素引入一個新的狀態。
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 (setQuery(event.target.value)} /> ); } export default App;{data.hits.map(item => (
- {item.title}
))}
目前,這兩個狀態彼此獨立,但現在希望將它們耦合起來,以獲取由input中的輸入來查詢指定的項目。通過下面的更改,組件應該在掛載之后通過查詢詞獲取所有數據。
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;
還差一部分:當你嘗試在input中輸入一些內容時,在mount之后就不會再獲取任何數據了,這是因為我們提供了空數組作為第二個參數,effect沒有依賴任何變量,因此只會在mount的時候觸發,但是現在的effect應該依賴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;
現在每當input的值更新的時候就可以重新獲取數據了。但這又導致了另一個問題:對于input中鍵入的每個字符,都會觸發該效果,并執行一個數據提取請求。如何提供一個按鈕來觸發請求,從而手動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 (setQuery(event.target.value)} /> ); }{data.hits.map(item => (
- {item.title}
))}
現在,effect依賴于于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相同,因為組件也在mount時獲取數據,因此結果應反映輸入字段中的值。但是,具有類似的query和search狀態有點令人困惑。為什么不將實際的URL設置為狀態而來代替search?
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 (setQuery(event.target.value)} /> ); }{data.hits.map(item => (
- {item.title}
))}
這就是使用effect hook獲取隱式編程數據的情況。你可以決定effect依賴于哪個狀態。一旦在點擊或其他effect中設置此狀態,此effect將再次運行。在這種情況下,如果URL狀態發生變化,effect將再次運行以從API獲取數據。
React Hooks和loading讓我們為數據獲取引入一個加載提示。它只是另一個由state hook管理的狀態。loading被用于在組件中渲染一個loading提示。
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 (setQuery(event.target.value)} /> {isLoading ? ( ); } export default App;Loading ...) : ({data.hits.map(item => (
)}- {item.title}
))}
一旦調用該effect進行數據獲取(當組件mount或URL狀態更改時發生),加載狀態將設置為true。一旦請求完成,加載狀態將再次設置為false。
React Hooks和錯誤處理如果在React Hooks中加上錯誤處理呢,錯誤只是用state hook初始化的另一個狀態。一旦出現錯誤狀態,應用程序組件就可以為用戶提供反饋。使用async/await時,通常使用try/catch塊進行錯誤處理。你可以在effect內做到:
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 (React在表單中獲取數據setQuery(event.target.value)} /> {isError && ); } export default App;Something went wrong ...} {isLoading ? (Loading ...) : ({data.hits.map(item => (
)}- {item.title}
))}
到目前為止,我們只有input和按鈕的組合。一旦引入更多的輸入元素,您可能需要用一個表單元素包裝它們。此外,表單還可以通過鍵盤上的“enter”來觸發。
function App() { ... return ({isError && ); }Something went wrong ...} ...
但是現在瀏覽器在單擊提交按鈕時頁面會重新加載,因為這是瀏覽器在提交表單時的固有行為。為了防止默認行為,我們可以通過event.preventDefault()取消默認行為。這也是在React類組件中實現的方法。
function App() { ... const doFetch = () => { setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`); }; return ({isError && ); }Something went wrong ...} ...
現在,當你單擊提交按鈕時,瀏覽器不會再重新加載。它和以前一樣工作,但這次使用的是表單,而不是簡單的input和按鈕組合。你也可以按鍵盤上的“回車”鍵。
自定義數據獲取hook為了提取用于數據獲取的自定義hook,請將屬于數據獲取的所有內容,移動到一個自己的函數中。還要確保能夠返回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]); const doFetch = () => { setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`); }; return { data, isLoading, isError, doFetch }; }
現在,你可以在App組件中使用新的hook了。
function App() { const [query, setQuery] = useState("redux"); const { data, isLoading, isError, doFetch } = useHackerNewsApi(); return (... ); }
接下來,從dofetch函數外部傳遞URL狀態:
const useHackerNewsApi = () => { ... useEffect( ... ); const doFetch = url => { setUrl(url); }; return { data, isLoading, isError, doFetch }; }; function App() { const [query, setQuery] = useState("redux"); const { data, isLoading, isError, doFetch } = useHackerNewsApi(); return (... ); }
初始狀態也可以變為通用狀態。把它簡單地傳遞給新的自定義hook:
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]); const doFetch = url => { setUrl(url); }; return { data, isLoading, isError, doFetch }; }; function App() { const [query, setQuery] = useState("redux"); const { data, isLoading, isError, doFetch } = useDataApi( "http://hn.algolia.com/api/v1/search?query=redux", { hits: [] }, ); return ({isError && ); } export default App;Something went wrong ...} {isLoading ? (Loading ...) : ({data.hits.map(item => (
)}- {item.title}
))}
這就是使用自定義hook獲取數據的方法。hook本身對API一無所知。它從外部接收所有參數,只管理必要的狀態,如數據、加載和錯誤狀態。它執行請求并將數據作為自定義數據獲取hook返回給組件。
Reducer的數據獲取hookreducer hook返回一個狀態對象和一個改變狀態對象的函數。dispatch函數接收type和可選的payload。所有這些信息都在實際的reducer函數中使用,從以前的狀態、包含可選payload和type的action中提取新的狀態。讓我們看看這在代碼中是如何工作的:
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函數和一個初始化的狀態對象作為參數,在我們的例子中,數據、加載和錯誤狀態的初始狀態的參數沒有改變,但是它們被聚合到由一個reducer hook管理的一個狀態對象,而不是單個state hook。
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來提取新狀態。畢竟,我們只有三種狀態轉換:初始化獲取過程,通知成功的數據獲取結果,以及通知錯誤的數據獲取結果。
在自定義hook的最后,狀態像以前一樣返回,但是因為我們有一個狀態對象,而不再是獨立狀態,所以需要用擴展運算符返回state。這樣,調用useDataApi自定義hook的用戶仍然可以訪問data、isloading和isError:
const useDataApi = (initialUrl, initialData) => { const [url, setUrl] = useState(initialUrl); const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, isError: false, data: initialData, }); ... const doFetch = url => { setUrl(url); }; return { ...state, doFetch }; };
最后,還缺少了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函數可以通過其參數訪問當前狀態和action。到目前為止,switch case語句中的每個狀態轉換只會返回原來的狀態。...語句用于保持狀態對象不變(意味著狀態永遠不會直接改變),現在,讓我們重寫一些當前狀態返回的屬性,以便在每次狀態轉換時更改狀態:
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(); } };
現在,每個狀態轉換(由操作的type決定)都將基于先前的狀態和可選的payload返回一個新的狀態。例如,在成功請求的情況下,payload用于設置新狀態對象的數據。
總之,reducer hook確保狀態管理的這一部分是用自己的邏輯封裝的。通過提供type和可選payload,你將始終已一個可預測的狀態結束。此外,你將永遠不會進入無效狀態。例如,以前可能會意外地將isloading和isError狀態設置為true。在這個案例的用戶界面中應該顯示什么?現在,reducer函數定義的每個狀態轉換都會導致一個有效的狀態對象。
在effect hook中禁止數據獲取即使組件已經卸載(例如,由于使用react路由器導航而離開),設置組件狀態也是react中的一個常見問題。我以前在這里寫過這個問題,它描述了如何防止在各種場景中為unmount的組件設置狀態。讓我們看看如何防止在自定義hook中為數據獲取設置狀態:
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]); const doFetch = url => { setUrl(url); }; return { ...state, doFetch }; };
每個effect hook都有一個clean功能,在組件卸載時運行。clean函數是從hook返回的一個函數。在我們的例子中,我們使用一個名為didCancel的布爾標志,讓我們的數據獲取邏輯知道組件的狀態(已裝載/未裝載)。如果組件已卸載,則標志應設置為“tree”,這將導致在最終異步解決數據提取后無法設置組件狀態。
注意:事實上,數據獲取不會中止——這可以通過axios的Cancellation實現——但是對于未安裝的組件,狀態轉換會不再執行。因為在我看來,axios的Cancellation并不是最好的API,所以這個防止設置狀態的布爾標志也能起到作用。
你已經了解了在React中state和effect hook如何用于獲取數據。如果您對使用render props和高階組件在類組件(和函數組件)中獲取數據很感興趣,請從一開始就去我的另一篇文章。否則,我希望本文對您了解react hook以及如何在現實場景中使用它們非常有用。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109309.html
摘要:它并不是實際在內部的工作方式,而且它只是一個提案,在未來都會有可能發生變化。這意味著,數據的存儲是獨立于組件之外的。因此,有一個訣竅就是你需要思考作為一組需要一個匹配一致的指針去管理的數組染陌譯。 原文地址:https://medium.com/@ryardley/... 譯文:染陌 (Github) 譯文地址:https://github.com/answershuto/Blog 轉...
摘要:更新日志應對添加以編程方式收集性能測量。在和在將棄用為常見攻擊面。添加對事件的支持。在從調用時發出警告,創建循環。在和從錯誤的渲染器使用時發出警告。 2019年8月8日,我們發布了React 16.9。它包含幾個新功能,錯誤修正和新的棄用警告,以幫助準備未來的主要版本。 showImg(https://segmentfault.com/img/bVbwoB5?w=1728&h=666)...
摘要:更新日志應對添加以編程方式收集性能測量。在和在將棄用為常見攻擊面。添加對事件的支持。在從調用時發出警告,創建循環。在和從錯誤的渲染器使用時發出警告。 2019年8月8日,我們發布了React 16.9。它包含幾個新功能,錯誤修正和新的棄用警告,以幫助準備未來的主要版本。 showImg(https://segmentfault.com/img/bVbwoB5?w=1728&h=666)...
摘要:原文作者譯者博軒于年月的中引入,作為在函數組件中使用狀態和生命周期的一種方法。雖然函數組件之前被稱為無狀態組件,但是的出現,使得這些函數組件可以使用狀態。因此,現在許多人將它們視為功能組件。 原文:What are React Hooks?作者:Robin Wieruch譯者:博軒 showImg(https://segmentfault.com/img/remote/14600000...
摘要:但是,你可能已經注意到,當你試圖通過指定依賴數組來優化時,可能會遇到帶有過時閉包的錯誤。這是否意味著閉包是問題所在我不這么認為。到目前為止,我所看到的所有情況下,過時的閉包問題都是由于錯誤地假設函數不更改或總是相同而發生的。 原文鏈接:https://overreacted.io/how-ar... 在很長一段時間內,標準答案是class components提供更多的特性(像sta...
閱讀 1117·2021-10-09 09:43
閱讀 18472·2021-09-22 15:52
閱讀 1058·2019-08-30 15:44
閱讀 3049·2019-08-30 15:44
閱讀 3243·2019-08-26 14:07
閱讀 903·2019-08-26 13:55
閱讀 2566·2019-08-26 13:41
閱讀 3086·2019-08-26 13:29