react中的watch監(jiān)視屬性-useEffect介紹
react的watch監(jiān)視屬性-useEffect
在vue中可以使用watch屬性,去監(jiān)視一個值,當(dāng)這個值進行變化的時候就去執(zhí)行一些操作。在react是沒有這個屬性的,但是它也一樣可以達到相同的效果,那么接下來看看它是怎么實現(xiàn)的呢?
在react中實現(xiàn)監(jiān)聽效果有一個比較簡單的方法,就是使用useEffect 這個hook,在我們剛接觸這個hook時經(jīng)常會被代入到類組件中的生命周期上,其實它不光有生命周期的功能更是可以實現(xiàn)監(jiān)聽的效果。
1、首先我們先來看下useEffect 用來當(dāng)作生命周期時是怎么使用的,代碼如下:
import React, { useEffect, useState } from 'react' export default function Wtach() { const [num, setNum] = useState(0) useEffect(() => { console.log('我是初始化的hook') return () => { console.log('我是卸載的hook') } }, []) useEffect(() => { console.log('我是更新的hook') }) return ( <div> <p>{`當(dāng)前數(shù)量是${num}`}</p> <button onClick={() => setNum(num + 1)}>增加數(shù)量</button> </div> ) }
上述代碼中,第一個useEffect 中傳入兩個參數(shù)第一個參數(shù)為一個callback回調(diào)函數(shù), 第二個參數(shù)傳入一個空數(shù)組,callback回調(diào)函數(shù)又返回了一個用于卸載組件時調(diào)用的函數(shù)。
他們的調(diào)用時期分別為初始化加載完成頁面調(diào)用useEffect 傳入的第一個函數(shù)(此時對應(yīng)omponentDidMount生命周期鉤子),在卸載的時候react會調(diào)用第一個callback函數(shù)里面返回的函數(shù)(此時對應(yīng)componentWillUnmount生命周期鉤子),這樣以來他們就完全實現(xiàn)了和生命周期一樣的兩個功能。
打印如下:
再來看第二個useEffect ,它只傳入了一個callback回調(diào)參數(shù),這個傳入的callback就會在每次頁面更新完成后進行調(diào)用(它對應(yīng)了componentDidUpdate生命周期鉤子)。
效果如下:
2、上面看完了useEffect 實現(xiàn)生命周期的功能,下面就來看下它是怎么實現(xiàn)類似watch屬性的。
代碼如下:
import React, { useEffect, useState } from 'react' export default function Wtach() { const [num, setNum] = useState(0) useEffect(() => { console.log('我是改變后的num', num) }, [num]) return ( <div> <p>{`當(dāng)前數(shù)量是${num}`}</p> <button onClick={() => setNum(num + 1)}>增加數(shù)量</button> </div> ) }
上述代碼useEffect 中除了正常傳入第一個callback函數(shù),第二個參數(shù)傳入一個數(shù)組里面的值是num,此時傳入的callback函數(shù)的調(diào)用就依賴了第二個參數(shù)數(shù)組中num的值,每當(dāng)num的值改變后react就會去調(diào)用傳入的第一個callback函數(shù),這樣就實現(xiàn)了類似watch屬性的功能。
效果如下:
最后如果你想要監(jiān)聽多個值,不需要寫多個useEffect ,只需要在第二個數(shù)組參數(shù)中增加新的值即可,如下:
useEffect使用指南
最近在寫一些React的應(yīng)用,用上了最新的Hooks。Hooks好用,但是對于剛上手Hooks的小伙伴來說,坑也挺多的。所以決定總結(jié)一下Hooks的使用經(jīng)驗,從useEffect開始。useEffect用于處理組件中的effect,通常用于請求數(shù)據(jù),事件處理,訂閱等相關(guān)操作。這里以數(shù)據(jù)請求為例,來深入介紹useEffect的用法。
最基本的使用
首先,舉一個簡單的例子:
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組件顯示了一個項目列表,狀態(tài)和狀態(tài)更新函數(shù)來自與useState這個hooks,通過調(diào)用useState,來創(chuàng)建App組件的內(nèi)部狀態(tài)。初始狀態(tài)是一個object,其中的hits為一個空數(shù)組,目前還沒有請求后端的接口。
為了獲取后端提供的數(shù)據(jù),接下來將使用axios來發(fā)起請求,同樣也可以使用fetch,這里會使用useEffect來隔離副作用。
import React, { useState, useEffect } from 'react'; import axios from 'axios'; ? function App() { ? const [data, setData] = useState({ hits: [] }); ? ? useEffect(async () => { ? ? const result = await axios( ? ? ? 'http://localhost/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中,不僅會請求后端的數(shù)據(jù),還會通過調(diào)用setData來更新本地的狀態(tài),這樣會觸發(fā)view的更新。
但是,運行這個程序的時候,會出現(xiàn)無限循環(huán)的情況。useEffect在組件mount時執(zhí)行,但也會在組件更新時執(zhí)行。因為我們在每次請求數(shù)據(jù)之后都會設(shè)置本地的狀態(tài),所以組件會更新,因此useEffect會再次執(zhí)行,因此出現(xiàn)了無限循環(huán)的情況。我們只想在組件mount時請求數(shù)據(jù)。我們可以傳遞一個空數(shù)組作為useEffect的第二個參數(shù),這樣就能避免在組件更新執(zhí)行useEffect,只會在組件mount時執(zhí)行。
import React, { useState, useEffect } from 'react'; import axios from 'axios'; ? function App() { ? const [data, setData] = useState({ hits: [] }); ? ? useEffect(async () => { ? ? const result = await axios( ? ? ? 'http://localhost/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的第二個參數(shù)可用于定義其依賴的所有變量。如果其中一個變量發(fā)生變化,則useEffect會再次運行。如果包含變量的數(shù)組為空,則在更新組件時useEffect不會再執(zhí)行,因為它不會監(jiān)聽任何變量的變更。
還有最后一個問題。在代碼中,我們使用async / await從第三方API獲取數(shù)據(jù)。如果你對async/await熟悉的話,你會知道,每個async函數(shù)都會默認返回一個隱式的promise。但是,useEffect不應(yīng)該返回任何內(nèi)容。這就是為什么會在控制臺日志中看到以下警告:
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函數(shù),因此,我們可以不直接調(diào)用async函數(shù),而是像下面這樣:
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://localhost/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;
響應(yīng)更新
上面的例子中,我們實現(xiàn)了再組件mount時請求數(shù)據(jù)。但是很多情況下,我們需要響應(yīng)用戶的輸入,然后再請求。這個時候我們會引入一個input框,監(jiān)聽query值的變化:
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://localhost/api/v1/search?query=redux', ? ? ? ); ? ? ? ? setData(result.data); ? ? }; ? ? ? fetchData(); ? }, []); ? ? return ( ? ? <Fragment> ? ? ? <input ? ? ? ? type="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;
有個query值,已經(jīng)更新query的邏輯,還需要將這個query值傳遞給后臺,這個操作會在useEffect中進行:
function App() { ? const [data, setData] = useState({ hits: [] }); ? const [query, setQuery] = useState('redux'); ? ? useEffect(() => { ? ? const fetchData = async () => { ? ? ? const result = await axios( ? ? ? ? `http://localhost/api/v1/search?query=${query}`, ? ? ? ); ? ? ? ? setData(result.data); ? ? }; ? ? ? fetchData(); ? }, []); ? ? return ( ? ? ... ? ); } ? export default App;
前面我們說了,目前的useEffect只會在組件mount時執(zhí)行,并且useEffect的第二個參數(shù)是依賴的變量,一旦這個依賴的變量變動,useEffect就會重新執(zhí)行,所以我們需要添加query為useEffect的依賴:
function App() { ? const [data, setData] = useState({ hits: [] }); ? const [query, setQuery] = useState('redux'); ? ? useEffect(() => { ? ? const fetchData = async () => { ? ? ? const result = await axios( ? ? ? ? `http://localhost/api/v1/search?query=${query}`, ? ? ? ); ? ? ? ? setData(result.data); ? ? }; ? ? ? fetchData(); ? }, [query]); ? ? return ( ? ? ... ? ); } ? export default App;
一旦更改了query值,就可以重新獲取數(shù)據(jù)。但這會帶來另一個問題:query的任何一次變動都會請求后端,這樣會帶來比較大的訪問壓力。這個時候我們需要引入一個按鈕,點擊這個按鈕再發(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://localhost/api/v1/search?query=${query}`, ? ? ? ); ? ? ? ? setData(result.data); ? ? }; ? ? ? fetchData(); ? }, [query]); ? ? return ( ? ? <Fragment> ? ? ? <input ? ? ? ? type="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> ? ); }
可以看到上面我們添加了一個新的按鈕,然后創(chuàng)建新的組件state:search。每次點擊按鈕時,會把search的值設(shè)置為query,這個時候我們需要修改useEffect中的依賴項為search,這樣每次點擊按鈕,search值變更,useEffect就會重新執(zhí)行,避免不必要的變更:
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://localhost/api/v1/search?query=${search}`, ? ? ? ); ? ? ? ? setData(result.data); ? ? }; ? ? ? fetchData(); ? }, [search]); ? ? return ( ? ? ... ? ); } ? export default App;
此外,search state的初始狀態(tài)設(shè)置為與query state 相同的狀態(tài),因為組件首先會在mount時獲取數(shù)據(jù)。所以簡單點,直接將的要請求的后端URL設(shè)置為search state的初始值。
function App() { ? const [data, setData] = useState({ hits: [] }); ? const [query, setQuery] = useState('redux'); ? const [url, setUrl] = useState( ? ? 'http://localhost/api/v1/search?query=redux', ? ); ? ? useEffect(() => { ? ? const fetchData = async () => { ? ? ? const result = await axios(url); ? ? ? ? setData(result.data); ? ? }; ? ? ? fetchData(); ? }, [url]); ? ? return ( ? ? <Fragment> ? ? ? <input ? ? ? ? type="text" ? ? ? ? value={query} ? ? ? ? onChange={event => setQuery(event.target.value)} ? ? ? /> ? ? ? <button ? ? ? ? type="button" ? ? ? ? onClick={() => ? ? ? ? ? setUrl(`http://localhost/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> ? ); }
如何處理Loading和Error
良好的用戶體驗是需要在請求后端數(shù)據(jù),數(shù)據(jù)還沒有返回時展現(xiàn)loading的狀態(tài),因此,我們還需要添加一個loading的state
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> ? ? ? <input ? ? ? ? type="text" ? ? ? ? value={query} ? ? ? ? onChange={event => setQuery(event.target.value)} ? ? ? /> ? ? ? <button ? ? ? ? type="button" ? ? ? ? onClick={() => ? ? ? ? ? setUrl(`http://localhost/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;
在useEffect中,請求數(shù)據(jù)前將loading置為true,在請求完成后,將loading置為false。我們可以看到useEffect的依賴數(shù)據(jù)中并沒有添加loading,這是因為,我們不需要再loading變更時重新調(diào)用useEffect。請記?。褐挥心硞€變量更新后,需要重新執(zhí)行useEffect的情況,才需要將該變量添加到useEffect的依賴數(shù)組中。
loading處理完成后,還需要處理錯誤,這里的邏輯是一樣的,使用useState來創(chuàng)建一個新的state,然后在useEffect中特定的位置來更新這個state。由于我們使用了async/await,可以使用一個大大的try-catch:
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://localhost/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> ? ? ? <input ? ? ? ? type="text" ? ? ? ? value={query} ? ? ? ? onChange={event => setQuery(event.target.value)} ? ? ? /> ? ? ? <button ? ? ? ? type="button" ? ? ? ? onClick={() => ? ? ? ? ? setUrl(`http://localhost/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;
每次useEffect執(zhí)行時,將會重置error;在出現(xiàn)錯誤的時候,將error置為true;在正常請求完成后,將error置為false。
處理表單
通常,我們不僅會用到上面的輸入框和按鈕,更多的時候是一張表單,所以也可以在表單中使用useEffect來處理數(shù)據(jù)請求,邏輯是相同的:
function App() { ? ... ? ? return ( ? ? <Fragment> ? ? ? <form ? ? ? ? onSubmit={() => ? ? ? ? ? setUrl(`http://localhost/api/v1/search?query=${query}`) ? ? ? ? } ? ? ? > ? ? ? ? <input ? ? ? ? ? type="text" ? ? ? ? ? value={query} ? ? ? ? ? onChange={event => setQuery(event.target.value)} ? ? ? ? /> ? ? ? ? <button type="submit">Search</button> ? ? ? </form> ? ? ? ? {isError && <div>Something went wrong ...</div>} ? ? ? ? ... ? ? </Fragment> ? ); }
上面的例子中,提交表單的時候,會觸發(fā)頁面刷新;就像通常的做法那樣,還需要阻止默認事件,來阻止頁面的刷新。
function App() { ? ... ? ? const doFetch = () => { ? ? setUrl(`http://localhost/api/v1/search?query=${query}`); ? }; ? ? return ( ? ? <Fragment> ? ? ? <form onSubmit={event => { ? ? ? ? doFetch(); ? ? ? ? ? event.preventDefault(); ? ? ? }}> ? ? ? ? <input ? ? ? ? ? type="text" ? ? ? ? ? value={query} ? ? ? ? ? onChange={event => setQuery(event.target.value)} ? ? ? ? /> ? ? ? ? <button type="submit">Search</button> ? ? ? </form> ? ? ? ? {isError && <div>Something went wrong ...</div>} ? ? ? ? ... ? ? </Fragment> ? ); }
自定義hooks
我們可以看到上面的組件,添加了一系列hooks和邏輯之后,已經(jīng)變得非常的龐大。而hooks的一個非常的優(yōu)勢,就是能夠很方便的提取自定義的hooks。這個時候,我們就能把上面的一大堆邏輯抽取到一個單獨的hooks中,方便復(fù)用和解耦:
function useFetchApi = () => { ? const [data, setData] = useState({ hits: [] }); ? const [url, setUrl] = useState( ? ? 'http://localhost/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://localhost/api/v1/search?query=${query}`); ? }; ? ? return { data, isLoading, isError, doFetch }; }
在自定義的hooks抽離完成后,引入到組件中
function App() { ? const [query, setQuery] = useState('redux'); ? const { data, isLoading, isError, doFetch } = useHackerNewsApi(); ? ? return ( ? ? <Fragment> ? ? ? ... ? ? </Fragment> ? ); }
然后我們需要在form組件中設(shè)定初始的后端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 ( ? ? <Fragment> ? ? ? <form ? ? ? ? onSubmit={event => { ? ? ? ? ? doFetch( ? ? ? ? ? ? `http://localhost/api/v1/search?query=${query}`, ? ? ? ? ? ); ? ? ? ? ? ? event.preventDefault(); ? ? ? ? }} ? ? ? > ? ? ? ? <input ? ? ? ? ? type="text" ? ? ? ? ? value={query} ? ? ? ? ? onChange={event => setQuery(event.target.value)} ? ? ? ? /> ? ? ? ? <button type="submit">Search</button> ? ? ? </form> ? ? ? ? ... ? ? </Fragment> ? ); }
使用useReducer整合邏輯
到目前為止,我們已經(jīng)使用了各種state hooks來管理數(shù)據(jù),包括loading、error、data等狀態(tài)。但是我們可以看到,這三個有關(guān)聯(lián)的狀態(tài)確是分散的,它們通過分離的useState來創(chuàng)建,為了有關(guān)聯(lián)的狀態(tài)整合到一起,我們需要用到useReducer。
如果你寫過redux,那么將會對useReducer非常的熟悉,可以把它理解為一個輕量額redux。useReducer 返回一個狀態(tài)對象和一個可以改變狀態(tài)對象的dispatch函數(shù)。跟redux類似的,dispatch函數(shù)接受action作為參數(shù),action包含type和payload屬性。我們看一個簡單的例子吧:
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, ? }); ? ? ... };
useReducer將reducer函數(shù)和初始狀態(tài)對象作為參數(shù)。在我們的例子中,data,loading和error狀態(tài)的初始值與useState創(chuàng)建時一致,但它們已經(jīng)整合到一個由useReducer創(chuàng)建對象,而不是多個useState創(chuàng)建的狀態(tài)。
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]); ? ? ... };
在獲取數(shù)據(jù)時,可以調(diào)用dispatch函數(shù),將信息發(fā)送給reducer。使用dispatch函數(shù)發(fā)送的參數(shù)為object,具有type屬性和可選payload的屬性。type屬性告訴reducer需要應(yīng)用哪個狀態(tài)轉(zhuǎn)換,并且reducer可以使用payload來創(chuàng)建新的狀態(tài)。在這里,我們只有三個狀態(tài)轉(zhuǎn)換:發(fā)起請求,請求成功,請求失敗。
在自定義hooks的末尾,state像以前一樣返回,但是因為我們拿到的是一個狀態(tài)對象,而不是以前那種分離的狀態(tài),所以需要將狀態(tài)對象解構(gòu)之后再返回。這樣,調(diào)用useDataApi自定義hooks的人仍然可以訪問data,isLoading 和 isError這三個狀態(tài)。
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函數(shù)的實現(xiàn)。它需要三種不同的狀態(tài)轉(zhuǎn)換FETCH_INIT,F(xiàn)ETCH_SUCCESS和FETCH_FAILURE。每個狀態(tài)轉(zhuǎn)換都需要返回一個新的狀態(tài)對象。讓我們看看如何使用switch case語句實現(xiàn)它:
? 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(); ? } };
取消數(shù)據(jù)請求
React中的一種很常見的問題是:如果在組件中發(fā)送一個請求,在請求還沒有返回的時候卸載了組件,這個時候還會嘗試設(shè)置這個狀態(tài),會報錯。我們需要在hooks中處理這種情況,可以看下是怎樣處理的:
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 }; };
我們可以看到這里新增了一個didCancel變量,如果這個變量為true,不會再發(fā)送dispatch,也不會再執(zhí)行設(shè)置狀態(tài)這個動作。這里我們在useEffe的返回函數(shù)中將didCancel置為true,在卸載組件時會自動調(diào)用這段邏輯。也就避免了再卸載的組件上設(shè)置狀態(tài)。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
react18?hooks自定義移動端Popup彈窗組件RcPop
這篇文章主要介紹了react18?hooks自定義移動端Popup彈窗組件RcPop,react-popup?基于react18+hook自定義多功能彈框組件,整合了msg/alert/dialog/toast及android/ios彈窗效果,需要的朋友可以參考下2023-08-08React Draggable插件如何實現(xiàn)拖拽功能
這篇文章主要介紹了React Draggable插件如何實現(xiàn)拖拽功能問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07