React18 的 useEffect 新特性為什么被瘋狂吐槽?

大家好,我是零一,react18 已經出來一段時間了,create-react-app 默認安裝的 React 版本也已經是 18+,不知道有沒有小伙伴發(fā)現(xiàn)自己有點看不懂 React 了?

import { useEffect, useState } from 'react'

function App () {
  const [data, setData] = useState(0)
 
  useEffect(() => {
    setData(preData => preData + 1)
  }, [])
 
  return (
    <div>{data}</div>
  )
}
看一下這段簡單的代碼,頁面最終展示的數(shù)字是幾?

是 1 這樣嗎?我覺得應該也是這樣,可事實就是在 React18 里,這并不是預期效果,最終展示的其實是 2,為什么呢?

useEffect 的"新特性"
根據(jù) React 最新的文檔[1] 中對于 useEffect 的介紹得知,之所以我們剛才的例子最終展示的是 2 而不是 1 的原因是,在 dev 環(huán)境下,React 會將每個組件掛載兩次進行測試。測試什么?測試你的 useEffect 有沒有潛在問題

大家都知道函數(shù)式組件掛載后,會執(zhí)行 useEffect 定義的副作用;在組件卸載時,會執(zhí)行 useEffect return 出來的回調執(zhí)行一些組件卸載時的行為,即:

function App () {
  useEffect(() => {
    console.log('組件掛載了')
    return () => {
      console.log('組件卸載了')
    }
  }, [])
 
  return (
    <div>useEffect</div>
  )
}
從組件掛載到卸載就會依次打?。?br>
組件掛載了
組件卸載了
而在 React18 里,是這樣打印的:

組件掛載了
組件卸載了
組件掛載了
按照文檔里所說的,之所有這么做的,是為了通過掛載兩次組件來提早發(fā)現(xiàn)你的問題,例如:

import { useEffect, useState } from 'react'

function App () {
  const [data, setData] = useState(0)
 
  useEffect(() => {
    setInterval(() => {
      setData(preData => preData + 1)
    }, 1000)
  }, [])
 
  return (
    <div>{data}</div>
  )
}
這段代碼時很多剛使用 React 的同學經常會犯的錯誤,在 useEffect 里定義了個定時器,但沒有在任何地方去清除它,所以即使在組件卸載了,這個定時器仍然還在運作,不光造成了內存泄漏,還可能會導致程序出現(xiàn)問題

所以就基于這段錯誤的代碼,React18 執(zhí)行 掛載 => 卸載 => 掛載,你就會發(fā)現(xiàn),實際是有兩個定時器在跑的,所以原本你想每秒 data + 1,變成了每秒 data + 2,如此明顯的問題一下就被我們發(fā)現(xiàn)了

那正確的做法就是在 useEffect 里 return 一個用于卸載時執(zhí)行的回調函數(shù):

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 (
    <div>{data}</div>
  )
}
這樣就沒有問題了。謝謝 React18 這個"獨特"的新特性(手動狗頭)

單單基于這個出發(fā)點,我覺得是非常好的,能幫我們提早發(fā)現(xiàn)問題,解決問題,而不是等發(fā)到線上后造成了性能問題,回過頭來再逐一排查。而且這只會在開發(fā)環(huán)境才會掛載兩次,生產環(huán)境還是正常的

但真的是個完美的特性嗎?根據(jù)網友的吐槽和我目前使用下來的感受,給我們造成的麻煩可能大于它本身的好處了

即使我的 useEffect 里根本沒有需要在卸載時清理的對象,它也會被執(zhí)行兩次,比如請求兩次、賦值兩次 ... 這似乎是給我們造成了不少的負擔啊,不知道的以為是別的地方出了 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(
- <React.StrictMode>
    <App />
- </React.StrictMode>
)
不過這樣也把其它的提示給一并干掉了,其實我是不想這么做的

只是這樣?
有很多人都在吐槽著!比如:初始化時 useEffect 會造成兩次請求的話,似乎我們也不該在 useEffect 中發(fā)起請求?

在這里插入圖片描述
然而 Dan 給出的解釋就是說,你應該在服務端渲染時就請求到數(shù)據(jù),而不是在客戶端渲染掛載了 DOM 后才請求數(shù)據(jù)

其實 React18 將在之后推出一些別的功能,這個模擬組件重新掛載的特性只是為之后的功能做準備的,具體是什么功能呢?類似于 Vue 的 KeepAlive[2]

最后
簡單總結一下:這個特性出發(fā)點是好的,同時也是為了之后的新特性做準備。但推出這個功能的同時也要考慮一下開發(fā)者的體驗(起碼是大部人的開發(fā)體驗),不然真的是得不償失。



作者:零一


歡迎關注微信公眾號 :前端印象