React 中,最優(yōu)雅的異步請求解決方案:use + Suspense
關(guān)于 React 19,我應(yīng)該算是國內(nèi)最早一個使用 React 19 的開發(fā)者。和許多還在觀望中的朋友不一樣,我已經(jīng)深度使用 React 19 開發(fā)了幾個項(xiàng)目了,
?
當(dāng)然,他的正式版還沒有發(fā)布。不過 RC 版更新非常頻繁,基本上兩三天就會發(fā)一個版本
雖然有部分項(xiàng)目沒有把 React 升級到 19 這個版本,但是我也將 React 19 中提到的開發(fā)思維抽離了出來,在低版本中使用。
這篇文章繼續(xù)給大家分享在 React 19 中使用 use + Suspense 來處理異步請求為什么是目前我見過的更優(yōu)雅的解決方案。
一、傳統(tǒng)方案
解決異步請求的方案中,我們要處理至少兩個最基本的邏輯。
- 正常的數(shù)據(jù)展示。
- 數(shù)據(jù)加載的 UI 狀態(tài)。
在 react 傳統(tǒng)的方案中,我們將數(shù)據(jù)和 Loading 定義在不同的 state 中,然后通過判斷 Loading 狀態(tài)的變化,來識別應(yīng)該返回什么樣的 UI 結(jié)果,然后借助 useEffect 來請求接口。
代碼大概如下所示:
很明顯,每個頁面都這樣干的話,會比較繁瑣。因此,我們通常會通過自定義 hook 的方式封裝請求邏輯,從而簡化每個頁面的代碼。
function useFetch() {
const [content, update] = useState({value: ''})
const [loading, setLoading] = useState(true)
useEffect(() => {
api().then(res => {
setLoading(false)
update(res)
})
}, [])
return {content, loading}
}
這樣,頁面代碼就變成了如下更簡潔的形式。
function Index2() {
const {content, loading} = useFetch()
if (loading) {
return <Skeleton />
}
return (
<Message message={content.value} />
)
}
?
大家常用的 ahooks,useQuery 等,都是這個封裝思路。
在 UI 層面,我們還可以做一層封裝,把 Loading 封裝到 UI 組件邏輯中去。常見的使用方式可以是這樣。
function Index2() {
const {content, loading} = useFetch()
return (
<Message
message={content.value}
loading={loading}
/>
)
}
也可以參考 antd 中,Spin 的使用方式。
function Index2() {
const {content, loading} = useFetch()
return (
<Spin tip="Loading...">
<Message message={content.value} />
</Spin>
)
}
通過這樣的兩步優(yōu)化讓我們的頁面代碼變得非常簡潔。這也是我在之前的版本中,使用最多的方式,開發(fā)效率也非常高。
剛開始使用這種方式時,我也非常驚訝,原來異步請求的邏輯處理可以整理得這么簡潔,這么得體。但是隨著使用經(jīng)驗(yàn)的增加,也處理了更多的場景,幾乎絕大多數(shù)場景都能夠平滑的應(yīng)對,但是我依然發(fā)現(xiàn)這種方式存在一些小小的痛點(diǎn)。
當(dāng)我們在思考如何封裝這個 useFetch 時,首先會考慮清楚在眾多場景之下,有哪些東西是變化量。變化的內(nèi)容我們將其設(shè)計(jì)為參數(shù)傳入。
function useFetch(params) {
...
}
常見的變化量包括:
- 入?yún)⒉煌?/li>
- 請求方式不同
- 返回類型不同
- 部分場景需要初始的默認(rèn)值
- 部分場景的接口并不需要立即請求
- 返回結(jié)果可能需要二次處理才能正常使用
- 參數(shù)變化之后的處理邏輯不同
- ...
當(dāng)不同的東西開始變得越來越多,我逐漸發(fā)現(xiàn)一個事情,那就是優(yōu)雅正在消失...
?
當(dāng)然最終我肯定是有對應(yīng)的成套架構(gòu)解決方案,但是這對于普通開發(fā)者來說會變得有點(diǎn)難度,需要更豐富的經(jīng)驗(yàn)來支撐才能應(yīng)對各種不同的場景。
二、React 19 的新方案
讓我感覺到比較驚喜的是,React 19 提出了一個新的方式,讓我們應(yīng)對這些復(fù)雜場景變得更加簡單。那就是 use + promise + Suspense。
首先來簡單學(xué)習(xí)一下 React19 中的做法。
首先,我們會把數(shù)據(jù)存儲在 promise 中。然后將 promise 定義為 state。
const _api3 = (params) => {
return new Promise(resolve => {
resolve({ value: 'React does not preserve any state for renders that got suspended before they were able to mount for the first time. When the component has loaded, React will retry rendering the suspended tree from scratch.' })
})
}
const [promise, setPromise] = useState(_api3)
如果有默認(rèn)參數(shù)需要傳入,只需要在執(zhí)行 _api3 時傳入?yún)?shù)即可。
const promise = useState(() => _api3({value: 10}))
如果我們在點(diǎn)擊時,需要修改參數(shù)并且重新請求接口,那么我們的做法也很簡單,一樣是重新執(zhí)行 _api3 即可。
function clickHandler() {
_api({value: 20})
}
由于觸發(fā) UI 更新必須要借助 state 的變化,因此,我們將每次 _api3 執(zhí)行返回的 promise 存儲在 sueState 中,點(diǎn)擊時,_api3 的執(zhí)行結(jié)果必定是新的 promise 對象,因此,代碼更改為如下,即可觸發(fā) UI 的更新。
function clickHandler() {
setPromise(_api({value: 20}))
}
然后,我們將 promise 傳入到具體的 UI 組件中去,并使用 Suspense 包裹起來。
export default function Index() {
const [promise, setPromise] = useState(_api3)
return (
<Suspense fallback={<Skeleton />}>
<Message promise={promise} />
</Suspense>
)
}
然后在 UI 組件內(nèi)部,使用 use 獲取 promise 中的數(shù)據(jù)即可。
const Message = (props) => {
const content = use(props.promise)
return (
<div className='flex border border-blue-100 p-4 rounded-md shadow'>
...
</div>
)
}
此時我們發(fā)現(xiàn),在這套解決方案之下,參數(shù)的多變性處理起來就變得非常容易。我們可以直接控制參數(shù)是否變化,也可以直接控制接口是否需要重新請求。
我們只需要按照需求,在響應(yīng)實(shí)踐中執(zhí)行對應(yīng)的邏輯,就可以了。而不需要像上面那種方案一樣,還要額外封裝,否則代碼就會變得很亂。
認(rèn)真體會這段代碼的優(yōu)雅性。
function clickHandler() {
setPromise(_api({value: 20}))
}
我們可以非常自由的在不同的場景處理參數(shù)。例如,有的地方可能需要緩存上一次的參數(shù),但是有的地方不需要。那么,需要緩存的場景,我們隨便單獨(dú)緩存即可。
也不用受限于參數(shù)的變化是否會引發(fā)接口的重新請求。這里參數(shù)的變化與接口的執(zhí)行被解耦開,直接由我們開發(fā)時控制
三、總結(jié)
很顯然,react19 中提到的解決異步邏輯的方案,是目前為止,我見到過的最優(yōu)雅的方案。這種方案不需要我們再進(jìn)一步二次封裝,就能夠輕松應(yīng)對各種復(fù)雜的場景。這必將成為未來開發(fā)的主流方案。
現(xiàn)在 react19 正式版本還沒有發(fā)布,加上熱衷于使用 react19 的博主只有我一個,因此這種方案目前只是在很小的范圍內(nèi)被使用,成為主流可能還需要一點(diǎn)時間的傳播和學(xué)習(xí)。