iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0
Modern Web

從Vue學React!不只要會用,還要真的懂~系列 第 10

【Day 10】ref和useState:定義與畫面渲染有關的State

  • 分享至 

  • xImage
  •  

前天和昨天花了一些時間了解Vue和React的「單向資料流」設計模式後,接著來看看與畫面渲染有關的state的部分,雖然在使用上,某些程度來說,Vue和React有點類似,但是其實在React的寫法及思考方向上,對於本來是習慣寫Vue的我來說,在一開始還是有些需要稍微熟悉一下的地方。

如何定義元件的state

首先,先從最簡單的地方開始看,也就是如何定義元件中的state。

Vue使用ref或reactive宣告變數

如果想要state在Vue的元件中能被監聽,就ㄧ定使用Vue的ref或reative,這樣在更新State時,Vue才可以監聽得到state的更新,並觸發元件重新渲染。

import { ref, reactive } from 'vue';
const vueRefState = ref('');
const vueReactiveState = reactive({ name: 'Phoebe' });

使用ref不論是物件型別或原始型別都會回傳一個ref物件,reactive則是只能使用在物件型別的值上。
https://ithelp.ithome.com.tw/upload/images/20230915/20130914RbYtH1Xm4C.png

  • 想進一步了解ref和reactive使用上的差異可以參考官方文件

React使用useState宣告變數

React也一樣,必須使用useState管理state,並透過setState來更新state,才能觸發元件重新渲染。與Vue比較不一樣的是使用useState時,會回傳一個陣列,陣列index 0的值為state,index 1的值則是改動這個state的setState函式,在這裡通常都是用解構賦值的方式,替陣列中的第一個值和第二值命名,第二的值因為是更新state的函式,所以通常都會以set為開頭來命名。
https://ithelp.ithome.com.tw/upload/images/20230915/20130914PKETBl1dmF.png

import { useState } from 'react';
const [reactState, setReactState] = useState('');

更新state的方式

知道怎麼定義state後,再來看看要怎麼更新state。在這部分,Vue和React也有差異。

vue用.value存取及更新

如果是使用ref宣告的state的話,需要使用.value來存取值及更新值;如果是用reactive宣告state的話,則可以直接像一般物件一樣存取值及更新值。

import { ref, reactive } from 'vue';
const vueRefState = ref('');
const vueReactiveState = reactive({ name: 'Phoebe' });

vueRefState.value = 'updateValue';
vueReactiveState.name = 'Jolin';

React使用setState函式更新值

想要更新React的state時,只需要使用useState所回傳的函式。

import { useState } from 'react';

const [reactState, setReactState] = useState('');
setReactState('newValue');

但這裡需要注意的是如果想要在setState之後,馬上取得新的state,例如以下這樣大家很直覺寫出來的寫法是行不通的!
(以下寫法為錯誤示範!)

import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const addCount = () => {
    setCount(count + 1);
    console.log(count);// 這裡印出來會是一開始的0
  };
  return (
    <div className="App">
      <div className="container">
        current count: { count }
        <button onClick={addCount}>add count</button>
      </div>
    </div>
  );
}

export default App;

React set state後,state不會立即更新!?batch state updates

我們都知道當我們setState的時候,會觸發畫面的重新渲染,但當一個事件中,有多個set state需要執行時,react並不會每呼叫一個set state就觸發ㄧ次重新渲染,而是整個event handler跑完後,才會進行重新渲染的動作,這也被稱為「batching」,也就是批量更新的意思。所以當你想要在set state執行後,就馬上印出更新後的state的話,實際上這時候的state其實並未更新。

這個部分就像是在向服務生點菜一樣,服務生不可能接收一樣客人的點餐,就送一次單,而是等你這一輪的餐點都點完後,再把最後點的餐點項目往廚房送。

再來一組程式碼來看看這到底是怎麼一回事!

import { useState } from 'react';
function App() {
  const [count, setCount] = useState(0);
  const addCount = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    console.log(count);
  };
  return (
    <div className="App">
      <div className="container">
        current count: { count }
        <button onClick={addCount}>add count</button>
      </div>
    </div>
  );
}

export default App;

可以先思考一下,當按下一次add count按鈕後,畫面上的current count會顯示什麼?console.log又會印出什麼?
如果不知道React的渲染機制中的小細節「batching」,應該會覺得印出來的4,畫面顯示也是4。但是如果用了解這個小細節之後,應該不難猜出正確結果應該什麼。

正確答案就是
畫面會顯示1
console.log則是印出0
https://i.imgur.com/sLemguL.gif

const addCount = () => {
  setCount(count + 1); // 0 + 1
  setCount(count + 1); // 0 + 1
  setCount(count + 1); // 0 + 1
  setCount(count + 1); // 0 + 1
  console.log(count); // 0
};

原因是雖然setCount(count + 1)在程式碼中出現了四次,但是實際上這四次的setCount(count + 1)裡面的count都是0,console.log印count的動作則是會在畫面重新渲染之前就執行,所以就變成console.log是印出0,畫面是顯示1

如果有需要馬上取得更新後的值怎麼辦?使用updater function

已前面提到的那了例子來說的話,如果想要在按下add count按鈕後,畫面上的count就變4,console.log也印出4的話,可以改成使用updater function的寫法。

  const addCount = () => {
    setCount(count + 1); // count 為 0
    setCount((previousCount) => previousCount + 1); // preCount為1
    setCount((previousCount) => previousCount + 1); // preCount為2
    setCount((previousCount) => previousCount + 1); // preCount為3
    setCount((previousCount) => {
      console.log(previousCount)// preCount為4
      return previousCount;
    });
  };

在上面這個範例中,previousCount => previousCount + 1的部分就是updater function,他可以記住前一次變更後的值來進行其他的處理,所以改成上述的寫法後,就可以讓每次setState的加1都有確實進行,也可以印出預期中被加到4的值!

React 18新增的automatic batching

除了批量更新batching這個在React 18之前就有的功能外,在React 18後,還新增了更強化的batching功能,也就是automatic batching。這項功能的新增後差異在於不論是一般的state更新,還是非同步內的state更新,都會被批量處理

這裡也可以看一下相關的例子(以下都是把strictMode拿掉的情境)
點擊按鈕都會呼叫以下這個函式

const fetchData = () => {
  fetch("https://api.escuelajs.co/api/v1/products")
  .then(function (response) {
    setCount(1);
    setNumber(2);
    return response.json();
  })
  .then(function (myJson) {
    console.log(myJson);
  });
};

React 18前,在這個情境下,會觸發兩次render。

  .then(function (response) {
    // 觸發一次render
    setCount(1);
    // 在觸發一次render
    setNumber(2);
    return response.json();
  })

https://i.imgur.com/JUz2mVV.gif

React 18開始,即使是非同步的情況,還是會統一批量處理setState,所以只會觸發一次render。

  .then(function (response) {
    setCount(1);
    setNumber(2);
    // 兩次setState一起觸發一次render
    return response.json();
  })

https://i.imgur.com/EgTh7SJ.gif

React及Vue State的重點差異 - 批量處理機制

Vue和React間的state差異除了之前有提到因為Vue的State是使用mutable特性的state,React的則是以immutable特性更新的state,使得state在取用及更新的上有所不同外,最重要的一點則是由於React在重新渲染時,會重新呼叫component function,為避免每次set state都會重複進行重新渲染的動作,所以在set state的部分帶有批量處理(batch)的機制,這部分也是我自己覺得與Vue的state最大不同的地方。雖然這部分對於熟悉Vue的人,在剛開始轉到React世界時,可能會踩到不少雷,也會產生不少疑問,但是只要其實只要從它最基本的渲染機制及特性下去理解,其實就不難理解為什麼會有這樣的特性在,以及如何善用及應對這樣的特性。

參考資料

React batches state updates
New Feature: Automatic Batching


上一篇
【Day 9】Vue & React上的單向資料流的應用
下一篇
【Day 11】Vue的雙向綁定v-model!React也辦得到嗎?
系列文
從Vue學React!不只要會用,還要真的懂~30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言