大家好屹徘,我是前端西瓜哥。
最近公司的項(xiàng)目用的 React 從 16 升到了 17 版本匕荸,選擇升級的原因是想以后將項(xiàng)目遷移到 Nextjs 上车摄。
結(jié)果發(fā)現(xiàn)因?yàn)?React 的行為不一致導(dǎo)致了一些看得見的和看不見的 bug,真的是一場災(zāi)難奏寨。
React 17 是一個(gè)比較特別的版本起意,它沒有任何新特性,但它改造了 React 的底層病瞳,讓 React 17 可以漸進(jìn)式地升級部分的模塊揽咕,為 18 版本做準(zhǔn)備。
這些改造套菜,有一部分是破壞性的亲善。
問題
我這里有個(gè)彈窗,彈窗里面是一些表單項(xiàng)逗柴。當(dāng)用戶在表單項(xiàng)做了一些修改蛹头,然后點(diǎn)擊彈窗的遮罩層時(shí),里面的組件會銷毀掉戏溺。
在銷毀時(shí)渣蜗,我們會調(diào)用一個(gè) ref 上的 save() 方法來保存這些數(shù)據(jù)。
useEffect(()?=>?{
??return?()?=>?{
????formRef.current?&&?formRef.current.save();
??}
},?[]);
在 React 16 的時(shí)候是正常的旷祸,但到 React 17耕拷,失敗了,我們無法保存表單里面的數(shù)據(jù)托享。
一頓排查之后骚烧,我找到了問題所在:在 React 17 版本,組件銷毀時(shí)獲取的 ref.current 可能會被重置為 null闰围。
接著我找到了官方文檔對于這種情況的說明:
https://zh-hans.reactjs.org/blog/2020/08/10/react-v17-rc.html#effect-cleanup-timing
useEffect 的清理時(shí)機(jī)
useEffect(()?=>?{
?return?()?=>?{
????//?這里的執(zhí)行清理操作
??}
})
在 React 17 中赃绊,副作用的執(zhí)行時(shí)機(jī)發(fā)生了變化,一個(gè)破壞性的效果是:如果組件卸載辫诅,副作用的清理時(shí)機(jī)是異步的凭戴,對應(yīng)的回調(diào)函數(shù)執(zhí)行也同樣是異步的。是的炕矮,異步么夫。
卸載時(shí)的要執(zhí)行的回調(diào)函數(shù)者冤,對于狀態(tài)和方法的訪問,問題不大档痪,它們是不可變的涉枫,能通過閉包的方式訪問到的。
但問題是 ref腐螟,它是可變的愿汰,我們可以隨意的設(shè)置 ref.current 的值,且不會觸發(fā)組件的重新渲染乐纸。
這個(gè) ref 會被 React 在組件卸載時(shí)重置為 null衬廷。因?yàn)槭钱惒降模晕覀冇泻艽罂赡軙蔡嵋粋€(gè) null 值汽绢。
這里有一個(gè)簡單的在線 demo吗跋,感興趣可以看看,當(dāng) Component 組件銷毀時(shí)宁昭,elRef 變成了 null:
https://codesandbox.io/s/react-17-zhong-zu-jian-xiao-hui-shi-ref-ke-neng-bei-she-zhi-wei-null-2kl4xu
然后是 React 16 版本的 ref跌宛,因?yàn)槭峭降模凿N毀時(shí) ref 沒有重置為 null:
https://codesandbox.io/s/16-de-ref-shi-zheng-chang-de-18mqmd
解決方案
官方的文檔提供了兩個(gè)解決方案积仗。
一個(gè)是用 useLayoutEffect疆拘。
useLayoutEffect(()?=>?{
??return?()?=>?{
????formRef.current?&&?formRef.current.save();
??}
},?[]);
useLayoutEffect 可以保證回調(diào)函數(shù) 同步 執(zhí)行,這樣就能確保 ref 此時(shí)還是最后的值寂曹,而不是被設(shè)置為 null哎迄。
第二種方式是用一個(gè)臨時(shí)變量在 ref 每次變化時(shí),將 ref.current 保存起來稀颁,放到副作用清理回調(diào)函數(shù)的閉包中芬失,來保證不可變性。
useEffect(()?=>?{
??const?instance?=?someRef.current;
??instance.someSetupMethod();
??return?()?=>?{
????instance.someCleanupMethod();
??};
});
但這里貌似還是有一點(diǎn)局限性:不能提供第二個(gè)參數(shù)匾灶,也就是依賴項(xiàng)參數(shù),因?yàn)槲覀儾荒鼙WC中途 ref 沒有發(fā)生改變租漂。
目前我是用第一種方案來處理我遇到的問題阶女。
結(jié)尾
版本升級這件事情,還是得權(quán)衡利弊哩治。