作者 | 零一
來源 | 前端印象
react18
已經出來一段時間了,create-react-app
默認安裝的 React 版本也已經是 18+
,不知道有沒有小夥伴發現自己有點看不懂 React 了?
import { useEffect, useState } from'react'
functionApp () {
const [data, setData] = useState(0)
useEffect(() => {
setData(preData => preData + 1)
}, [])
return ({data}
)
}
看一下這段簡單的程式碼,頁面最終展示的數字是幾?
是 1
這樣嗎?我覺得應該也是這樣,可事實就是在 React18 裡,這並不是預期效果,最終展示的其實是 2
,為什麼呢?
useEffect 的”新特性”
根據 React 最新的文件[1] 中對於 useEffect
的介紹得知,之所以我們剛才的例子最終展示的是 2
而不是 1
的原因是,在 dev
環境下,React 會將每個元件掛載兩次進行測試。測試什麼?測試你的 useEffect
有沒有潛在問題
大家都知道函數式元件掛載後,會執行 useEffect
定義的副作用;在元件卸載時,會執行 useEffect
return 出來的回調執行一些元件卸載時的行為,即:
function
App
() {
useEffect(() => {
console.log('元件掛載了')
return() => {
console.log('元件卸載了')
}
}, [])
return (useEffect
)
}
從元件掛載到卸載就會依次列印:
元件掛載了
元件卸載了
而在 React18 裡,是這樣列印的:
元件掛載了
元件卸載了
元件掛載了
按照文件裡所說的,之所有這麼做的,是為了通過掛載兩次元件來提早發現你的問題,例如:
import { useEffect, useState } from'react'
functionApp () {
const [data, setData] = useState(0)
useEffect(() => {
setInterval(() => {
setData(preData => preData + 1)
}, 1000)
}, [])
return ({data}
)
}
這段程式碼時很多剛使用 React 的同學經常會犯的錯誤,在 useEffect
裡定義了個定時器,但沒有在任何地方去清除它,所以即使在元件卸載了,這個定時器仍然還在運作,不光造成了記憶體洩漏,還可能會導致程序出現問題
所以就基於這段錯誤的程式碼,React18 執行 掛載 => 卸載 => 掛載
,你就會發現,實際是有兩個定時器在跑的,所以原本你想每秒 data + 1
,變成了每秒 data + 2
,如此明顯的問題一下就被我們發現了
那正確的做法就是在 useEffect
裡 return
一個用於卸載時執行的回呼函式:
import { useEffect, useState } from 'react'
function App () {
const [data, setData] = useState(0)
useEffect(() => {
+ const timer = setInterval(() => {
setData(preData => preData + 1)
}, 1000)
+ return () => clearInterval(timer)
}, [])
return (
{data}
)
}
這樣就沒有問題了。謝謝 React18 這個”獨特”的新特性(手動狗頭)
單單基於這個出發點,我覺得是非常好的,能幫我們提早發現問題,解決問題,而不是等發到線上後造成了性能問題,回過頭來再逐一排查。而且這隻會在開發環境才會掛載兩次,生產環境還是正常的
但真的是個完美的特性嗎?根據網友的吐槽和我目前使用下來的感受,給我們造成的麻煩可能大於它本身的好處了
即使我的 useEffect
里根本沒有需要在卸載時清理的對象,它也會被執行兩次,比如請求兩次、賦值兩次 … 這似乎是給我們造成了不少的負擔啊,不知道的以為是別的地方出了 bug 呢!
關閉特性
我也可以手動關閉這個特性,找到入口檔案 main.tsx
,把 StrictMode
標籤給去掉就好了
mport React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
-
-
)
不過這樣也把其它的提示給一併幹掉了,其實我是不想這麼做的
只是這樣?
有很多人都在吐槽著!比如:初始化時 useEffect
會造成兩次請求的話,似乎我們也不該在 useEffect
中發起請求?

然而 Dan 給出的解釋就是說,你應該在服務端渲染時就請求到資料,而不是在客戶端渲染掛載了 DOM 後才請求資料
其實 React18 將在之後推出一些別的功能,這個模擬元件重新掛載的特性只是為之後的功能做準備的,具體是什麼功能呢?類似於 Vue 的 KeepAlive[2]
最後
簡單總結一下:這個特性出發點是好的,同時也是為了之後的新特性做準備。但推出這個功能的同時也要考慮一下開發者的體驗(起碼是大部人的開發體驗),不然真的是得不償失。
對於 useEffect
這個新特性,你怎麼看?歡迎在評論區留言!
參考資料
[1]
React 最新的文件: https://beta-reactjs-org-git-effects-fbopensource.vercel.app/learn/synchronizing-with-effects
[2]
KeepAlive: https://vuejs.org/guide/built-ins/keep-alive.html#basic-usage