今天朋友用useEffect模擬componentDidMount和componentWillUnmount時
遇到了些問題而跑來跟我分享,覺得頗有收獲,在此紀錄一下
首先先來看code
邏輯就是點了按鈕後Y會消滅,生成X,反之亦然
然後使用useEffect
在元件生成時執行console.log(“mount:”, current)(模擬componentDidMount)
在元件消滅前執行console.log(“unmount:”, current)(模擬componentWillUnmount)
預期是mount和unmount交互列印
點了之後卻發現console欄不論怎麼點擊按鈕只顯示mount: Y
以下就來看看怎麼解決這個問題
1.將props放入第二參數
這裡用了useRef是為了關掉警告,如果普通大概會這樣寫
const SideEffect = ({ text }) => { useEffect(() => { console.log("mount:", text); return () => console.log("unmount:", text); }, []); return null;};
然後有開eslint的就會接到警告了
React Hook useEffect has a missing dependency: 'text'. Either include it or remove the dependency array. (react-hooks/exhaustive-deps)
這裡就把text加進useEffect第二參數的陣列中
表示text發生變化時re-render
const SideEffect = ({ text }) => { useEffect(() => { console.log("mount:", text); return () => console.log("unmount:", text); }, [text]); return null;};
這時會有人說:啊你不是說要模擬componentDidMount和componentWillUnmount嗎?這兩個函數能帶參數嗎?
於是接下來不改動SideEffect
元件,而只在App
中更改
原本預期的是
console.log(“mount:”, current)
和console.log(“unmount:”, current)
交互列印
然而這件事並沒有發生,這其實就代表元件沒有re-render,而是更新資料而已,原因是
<> <h1>X</h1> <SideEffect text="X" /></>
這兩個元件
<> <h1>Y</h1> <SideEffect text="Y" /></>
在React眼裡都是長這樣
fragment
h1
SideEffect ()
既然是同樣的東西,那麼只更新資料自然是比較好的選擇
2.將原本的三元運算子以&&改寫
{!value && ( <> <h1>X</h1> <SideEffect text="X" /> </>) }{value && ( <> <h1>Y</h1> <SideEffect text="Y" /> </>) }
這麼寫的話component tree會有兩種可能
// 第1種
fragment
h1
SideEffect (X)
false// 第2種
false
fragment
h1
SideEffect (Y)
由於結構完全不同,這裡就會re-render
當然又會有人說:這樣寫好醜,原本的三元運算子不行嗎?
3.跟React標示這是不同的元件
上面有提到,原本的寫法在component tree中看起來永遠是這樣子
fragment
h1
SideEffect ()
因此要用別的方式跟React表示這兩個是不同的元件,而不是同一個元件,React就不會用更新資料的方式處理
<Fragment key="X"> <h1>X</h1> <SideEffect text="X" /></Fragment>
也就是加上key就可以解決了
這裡要注意如果要把<> </>
加上key就要把Fragment插進來並寫出來
成果: