有一種復雜場景 React 新手經常處理不好。
那就是一個頁面有多個模塊,每個模塊都有自己的數據需要請求。與此同時,可能部分模塊的數據還要依賴父級的異步數據才能正常請求自己的數據。如下圖所示,當我們直接訪問該頁面時,頁面請求的數據就非常多。而且這些數據還有一定的先后依賴關系。
和之前的方案一樣,我們先定義父組件的請求接口。
const getMessage = async () => { const res = await fetch('https://api.chucknorris.io/jokes/random') return res.json()}
然后在父組件中,將 getMessage() 執行之后返回的 promise 作為狀態存儲在 useState 中。這樣,當我點擊時,只需要重新執行依次 getMessage() 就可以更新整個組件。
const [ messagePromise, setMessagePromise] = useState(null)
但是此時我們發現,messagePromise 并沒有初始值,因此初始化時,接口并不會請求。這種情況下,有兩種交互我們需要探討。一種是通過點擊按鈕來初始化接口。另外一種就是組件首次渲染就要初始化接口。
我們之前的案例中,使用了取巧的方式,在函數組件之外提前獲取了數據,這會導致訪問任何頁面該數據都會加載,因此并非合適的手段。
// 我們之前的案例這樣做是一種取巧的方式const api = getMessage()function Message() { ...
但是如果我們直接把 getMessage() 放在組件內部執行,也存在不小的問題。因為當組件因為其他的狀態發生變化需要重新執行時,此時 getMessage() 也會冗余的多次執行。
// 此時會冗余多次執行const [ messagePromise, setMessagePromise] = useState(getMessage())
理想的情況是 getMessage() 只在組件首次渲染時執行依次,后續狀態的改變就不在執行。而不需要多次執行。
我們先來考慮通過點擊事件初始化接口的交互。此時我們可以先設置 messagePrmoise 的初始值為 null。
const [ messagePromise, setMessagePromise] = useState(null)
不過這樣做有一個小問題就是如果我將 messagePromise 值為 null 時傳遞給了子組件。那么子組件就會報錯,因此我們需要特殊處理。一種方式就是在子組件內部判斷。
const MessageOutput = ({messagePromise}) => { if (!messagePromise) return const messageContent = use(messagePromise)
或者:
// 這種寫法是在需要默認顯示狀態時的方案const MessageOutput = ({messagePromise}) => { const messageContent = messagePromise ? use(messagePromise) : {value: '默認值'}
另外一種思路就是設置一個狀態,子組件基于該狀態的值來是否顯示。然后在點擊時將其設置為 true。
const [show, setShow] = useState(false)function __clickHandler() { setMessagePromise(getMessage()) setShow(true)}
{show && <MessageContainer messagePromise={messagePromise} />}
另外一種交互思路就是初始化時就需要馬上請求數據。此時我們為了確保 getMessage() 只執行一次,可以新增一個非 state 狀態來記錄組件的初始化情況。默認值為 false,初始化之后設置為 true。
const i = useRef(false)let __api = i.current ? null : getMessage()const [ messagePromise, setMessagePromise] = useState(null)
然后在 useEffect 中,將其設置為 true,表示組件已經初始化過了。
useEffect(() => { i.current = true}, [])
這是利用 useState 的內部機制,初始化值只會賦值一次來做到的。從而我們可以放心更改后續 __api 的值為 null.
從這個細節的角度來說,函數組件多次執行的確會給開發帶來一些困擾,Vue3/Solid 只執行一次的機制會更舒適一些,不過處理得當也能避免這個問題。
接下來,我們需要考慮的就是 Suspense 嵌套執行的問題就行了。這個執行起來非常簡單。我們只需要將有異步請求的模塊用 Suspense 包裹起來當成一個子組件。然后該子組件可以當成一個常規的子組件作為 Suspense 組件的子組件。
例如,我們聲明一個子組件如下所示:
const getApi = async () => { const res = await fetch('https://api.chucknorris.io/jokes/random') return res.json()}export default function Index(props) { const api = getApi() return ( <div> <div id='tips'>多個 Suspense 嵌套,子組件第一部分</div> <div className="content"> <div className='_05_dou1_message'>父級消息: {props.value}</div> <Suspense fallback={<div>Loading...</div>}> <Item api={api} /> </Suspense> </div> </div> )}const Item = ({api}) => { const joke = api ? use(api) : {value: 'nothing'} return ( <div className='_03_a_value_update'>子級消息:{joke.value}</div> )}
然后我可以將這個子組件放在 Suspense 內就可以了。
import DouPlus1 from './Dou1'import DouPlus2 from './Dou2'
const MessageOutput = ({messagePromise}) => { const messageContent = use(messagePromise) return ( <div> <p>{messageContent.value}</p> <DouPlus1 value={messageContent.value} /> <DouPlus2 value={messageContent.value} /> </div> )}
在另外一個子組件中,我們還設計了內部狀態,用于實現切換按鈕,來增加頁面交互的復雜度。并且每次切換都會請求接口。
如果切換時,上一個接口沒有請求完成,React 會自己處理好數據的先后問題。不需要我們額外考慮競態條件的情況。完整代碼如下:
var tabs = ['首頁', '視頻', '探索']export default function Index() { var r = useRef(false) var api = r.current ? null : getApi() const [promise, setPromise] = useState(api) const [current, setCurrent] = useState(0) useEffect(() => { r.current = true }, []) return ( <div> <div id='tips'>多個 Suspense 嵌套,子組件第二部分</div> <div className="content"> {tabs.map((item, index) => ( <button id='btn_05_item' className={current == index ? 'active' : ''} onClick={() => { setCurrent(index) setPromise(getApi()) }} key={item} >{item}</button> ))} <Suspense fallback={<div className='_05_a_value_item'>Loading...</div>}> <Item api={promise} /> </Suspense> </div> </div> )}const Item = ({api}) => { const joke = use(api) return ( <div className='_05_a_value_item'>{joke.value}</div> )}
當我們要在復雜交互的情況下使用嵌套 Suspense 來解決問題,如果我們組件劃分得當、與數據依賴關系處理得當,那么代碼就會相當簡單。不過這對于開發者來說,會有另外一個層面的要求。那就是如何合理的處理好組件歸屬問題。
許多前端頁面開發難度往往都是由于組件劃分不合理,屬性歸屬問題處理不夠到位導致的。因此 Suspense 在這個層面有了一個剛需,開發者必須要具備合理劃分組件的能力,否則即使使用了 Suspense,也依然可能導致頁面一團混亂。
本文鏈接:http://www.tebozhan.com/showinfo-26-93090-0.html震驚!用 Suspense 解決請求依賴的復雜場景居然這么簡單!
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: C++ 首度超越 C 語言僅次榜首 Python,TIOBE 編程指數六月排行榜公布
下一篇: PHP 服務實現性能剖析、跟蹤和可觀察性14444444444444】=102102102102102102102102102102102102102102102102實踐