iT邦幫忙

2023 iThome 鐵人賽

DAY 2
0
Modern Web

30天React練功坊-攻克常見實務/面試問題系列 第 2

30天React練功坊-攻克常見實務/面試問題 Day2: setState isn't working correctly when called multiple times

  • 分享至 

  • xImage
  •  
tags: ItIron2023 react

前言

昨天我們用了一個非常基本的問題了解了關於react的render機制以及useState可能會碰到的一個小問題,這幾天我們會暫時離不開這個hook,馬上來看一下今天的問題吧!
順帶一提,我會盡可能地讓題目難度隨著時間遞增,所以如果你覺得這玩意真的他媽太簡單了,可以試著過幾天再回來追這系列文!

本日題目

馬上就開始吧,請觀察這個codesandbox內的程式碼。

day2-demo-image

今天我們因為某種原因,期望點擊按鈕後一口氣呼叫5次的setCount讓count的值遞增5,但實際上的執行結果卻出乎我們意料,每一次的點擊都只讓count增加1,仿佛我們只寫了一個setCount一般,你是否能解釋並修復這個異常,讓點擊後順利的遞增我們setCount的次數呢?

const handleClick = () => {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
};

解答與基本解釋

看起來雖然是個簡單的useState問題,但也許答案並不是你想得那樣,我之前在碰到人向我提問這類問題時,他們一個很常見的誤解就是以為這是因為setState是非同步行為,實際上問題並沒有這麼玄妙,setState也100%是個同步的操作,僅是你對react渲染仍不夠瞭解而已!

最根本就根本的原因其實在於state在每一次的render其實都是維持相同的值(簡單說是因為在closure內),什麼? 你聽不懂? 加上log我想你就會清楚一些了。

const handleClick = () => {
  setCount(count + 1);
  console.log('count1', count) // count1 0
  setCount(count + 1);
  console.log('count2', count) // count2 0
  setCount(count + 1);
  console.log('count3', count) // count3 0 
  setCount(count + 1);
  console.log('count4', count) // count4 0 
  setCount(count + 1);
  console.log('count5', count) // count5 0 
};

注意到了嗎? 雖然你call setState了,但當下印出每次的值都是初始值0,這下你理解了,實際上上方的程式碼與下方的等價

const handleClick = () => {
  setCount(0 + 1); 
  setCount(0 + 1);
  setCount(0 + 1);
  setCount(0 + 1);
  setCount(0 + 1);
};

在下次render前,你count的值都不會有任何變動,值本身是immutable的。 既然當下的值是0,你每次去設0+1自然結果都會是1囉!了解原因後我想你就比較好辦了,我們要做的就是利用前一次的count值當基礎再加上1就行了,因此改為callback的形式就會一切正常囉!

const handleClick = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
};

在這樣的情況下,每一次都會以前一次的count值作為基礎再加上1,另一方面react也會batch這些setState的操作,讓他們能在下一次的rerender前完成,最終就是一口氣看到5囉!

總結

今天我們稍微更深入的探討了react的render行為以及常見的誤解,在初學看來也許會覺得react有些不合理(事實也的確如此),但這就是react底層的運作機制,隨著題目繼續進行下去相信你對於整個渲染行為會有更深的理解,未來遇到類似的問題也會較為容易切入,我們明天見!

本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!


上一篇
30天React練功坊-攻克常見實務/面試問題 Day1: setState isn't working with push method
下一篇
30天React練功坊-攻克常見實務/面試問題 Day3: Uncaught TypeError: Cannot set properties of undefined
系列文
30天React練功坊-攻克常見實務/面試問題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
blank
iT邦新手 4 級 ‧ 2024-01-18 11:57:40

因為我才剛接觸react約1週,可能理解上有問題
主要是對你說setState是同步的這點有所疑問?

  1. react他接收到set function後先進到re-render phase
  2. 此時會去比較virtual DOM tree跟fiber tree的區別,建立出list of DOM updates
  3. 再進入commit phase,看哪些DOM要做更新
    這部分不就是非同步的嗎?在fiber tree更新前,程式碼就往下走了
 function handleInc() {
    console.log(likes); // 0
    setLikes(likes + 1);
    console.log(likes); // 0
    setLikes((likes) => likes + 1);
    console.log(likes); // 0
  }

blank 非常棒的問題!有多少剛接觸react的人有辦法講出fiber tree這玩意,看得出你很用心在學,先給你個讚!我分別回答你兩個問題,我們先看第一個吧。

setState怎麼會是同步的?

正確說來是呼叫setState更新狀態這件事情是同步的,但因為react本身的一些優化機制(例如batch update)讓它看起來像是非同步操作,而狀態更新與組件的渲染行為就是100%的非同步操作。
這是個大家已經爭論已久的問題,也是在面試時被問到有可能會吵架的東西,慢慢的有些人變得不是這麼在意這些細微的差異,而是把關注放在最終的執行結果上,我這邊礙於篇幅沒辦法跟你說明得太過於詳細,你有興趣的話可以用react setState synchronous之類的關鍵字搜尋看看,繁體中文針對這點的討論真的不算太多,很多人就是把它當作完全的非同步就這樣過去了。

要去哪裡看這些底層的東西?

我的話會看一些文章分析的參考來源,其中不免就會提到react本身的repo可以翻原始碼,或是有些youtube影片也會做這類的主題探討,我記得有一部是叫Deep dive to react的系列

blank iT邦新手 4 級 ‧ 2024-01-19 13:14:14 檢舉

你有興趣的話可以用react setState synchronous之類的關鍵字搜尋看看

我後來看這篇 所以我可以理解呼叫setState更新狀態這件事情是同步的這一個事實,而後續的狀態更新與組件渲染是非同步的,那這樣就是代表setState內部有類似非同步的callback function嗎?

我記得有一部是叫Deep dive to react的系列

了解了~我覺得身為非本科轉職的junior,在認真弄清楚語法的底層邏輯還有學習新框架這點真的會很糾結。有點像是無限迴圈的感覺,學新的框架慢就是因為對於這些基本觀念還不夠了解,但是在學這些基本觀念時,又會有種焦躁感。畢竟學習過的成就感跟學框架後做一些東西的成就感還是有落差XD,前者就像是非同步,要累積一段時間才會整個湧現,後者就是很直觀的同步。有去看一下你的部落格,看完這篇後覺得收穫很多~

我要留言

立即登入留言