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

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

import { useEffect, useState } from 'react'

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

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

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

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

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

組件掛載了
組件卸載了
組件掛載了
按照文檔里所說(shuō)的,之所有這么做的,是為了通過(guò)掛載兩次組件來(lái)提早發(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>
  )
}
這段代碼時(shí)很多剛使用 React 的同學(xué)經(jīng)常會(huì)犯的錯(cuò)誤,在 useEffect 里定義了個(gè)定時(shí)器,但沒有在任何地方去清除它,所以即使在組件卸載了,這個(gè)定時(shí)器仍然還在運(yùn)作,不光造成了內(nèi)存泄漏,還可能會(huì)導(dǎo)致程序出現(xiàn)問題

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

那正確的做法就是在 useEffect 里 return 一個(gè)用于卸載時(shí)執(zhí)行的回調(diào)函數(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 這個(gè)"獨(dú)特"的新特性(手動(dòng)狗頭)

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

但真的是個(gè)完美的特性嗎?根據(jù)網(wǎng)友的吐槽和我目前使用下來(lái)的感受,給我們?cè)斐傻穆闊┛赡艽笥谒旧淼暮锰幜?br>
即使我的 useEffect 里根本沒有需要在卸載時(shí)清理的對(duì)象,它也會(huì)被執(zhí)行兩次,比如請(qǐng)求兩次、賦值兩次 ... 這似乎是給我們?cè)斐闪瞬簧俚呢?fù)擔(dān)啊,不知道的以為是別的地方出了 bug 呢!

關(guān)閉特性
我也可以手動(dòng)關(guān)閉這個(gè)特性,找到入口文件 main.tsx,把 StrictMode 標(biāo)簽給去掉就好了

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>
)
不過(guò)這樣也把其它的提示給一并干掉了,其實(shí)我是不想這么做的

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

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

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

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



作者:零一


歡迎關(guān)注微信公眾號(hào) :前端印象