前天和昨天花了一些時間了解Vue和React的「單向資料流」設計模式後,接著來看看與畫面渲染有關的state的部分,雖然在使用上,某些程度來說,Vue和React有點類似,但是其實在React的寫法及思考方向上,對於本來是習慣寫Vue的我來說,在一開始還是有些需要稍微熟悉一下的地方。
首先,先從最簡單的地方開始看,也就是如何定義元件中的state。
如果想要state在Vue的元件中能被監聽,就ㄧ定使用Vue的ref或reative,這樣在更新State時,Vue才可以監聽得到state的更新,並觸發元件重新渲染。
import { ref, reactive } from 'vue';
const vueRefState = ref('');
const vueReactiveState = reactive({ name: 'Phoebe' });
使用ref不論是物件型別或原始型別都會回傳一個ref物件,reactive則是只能使用在物件型別的值上。
React也一樣,必須使用useState管理state,並透過setState來更新state,才能觸發元件重新渲染。與Vue比較不一樣的是使用useState時,會回傳一個陣列,陣列index 0的值為state,index 1的值則是改動這個state的setState函式,在這裡通常都是用解構賦值的方式,替陣列中的第一個值和第二值命名,第二的值因為是更新state的函式,所以通常都會以set為開頭來命名。
import { useState } from 'react';
const [reactState, setReactState] = useState('');
知道怎麼定義state後,再來看看要怎麼更新state。在這部分,Vue和React也有差異。
如果是使用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的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;
我們都知道當我們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
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
。
已前面提到的那了例子來說的話,如果想要在按下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的值!
除了批量更新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();
})
React 18開始,即使是非同步的情況,還是會統一批量處理setState,所以只會觸發一次render。
.then(function (response) {
setCount(1);
setNumber(2);
// 兩次setState一起觸發一次render
return response.json();
})
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