2-8 React 元件中的資料狀態 - useState 的使用

在上一個單元中我們提到,雖然變數已經更新了,但因為 React 並不知道這件事,所以他不知道要幫我們更新畫面,顯示最新的資料。

好在 React 提供了方法可以來監控並改變這些資料,一旦使用 React 中提供的方法來修改資料時,React 一發現到資料內容有變動時,就會自動更新畫面,而這個方法就是這裡要提到的第一個 React Hooks - useState

狀態(state)是什麼#

這個方法之所以叫做 useState 是因為在 React 元件中,這些會連動導致畫面改變的「資料(data)」習慣上被稱作「狀態(state)」。以紅綠燈來說,假設有一個資料可以用來表示紅綠燈的顏色,0 是紅燈、1 是黃燈、2 是綠燈,當這個資料是 0 的時候,燈號就會變成紅燈的「狀態」;當資料變成 1 時,燈號就會變成「黃燈」的狀態,因此會把這些可以造成畫面變更的資料,稱作狀態(state)。

當你以後聽到開發者在討論某個元件的「狀態」時,通常不是指元件有沒有生病或依然健在的那個狀態,而是在說現在的「資料」是長什麼樣子。

note

💡 Tips:在 React 中講到「狀態(state)」時,一般你可以直接把它成「資料(data)」來理解。

至於 useState 的方法前面之所以會多了個 use ,是因為這是在 React Hooks 中的慣例,只要開頭為 use 的函式,就表示它是個 "Hook"。先讓我們來看一下怎麼使用 useState 這個 React Hooks,之後再來對 Hooks 做更多的說明。

換你了:在計數器中使用 useState#

這裡先來直接實作,過程中可能會有一些你不太了解的程式碼沒關係,先做出效果來,後面會再做說明。

過程主要包含四個步驟:

  • STEP 1:從 React 物件中取出 useState 方法
const { useState } = React;
const Counter = () => /* ... */
  • STEP 2:呼叫 useState 方法後可以取得一個「變數(count)」和「改變該變數的方法(setCount)」,useState 中的參數是 count 的預設值,這裡設為 256
const Counter = () => {
const [count, setCount] = useState(256);
return {
/* ... */
};
};
  • STEP 3:在使用者點擊向上箭頭時,透過 setCount 方法將變數 count 加 1
<div className="chevron chevron-up" onClick={() => setCount(count + 1)} />
  • STEP 4:在使用者點擊向下箭頭時,透過 setCount 方法將變數 count 減 1
<div className="chevron chevron-down" onClick={() => setCount(count - 1)} />

你可以到下方連結直接檢視修改後完整的程式碼,或於 Github 專案說明頁點擊連結「useState 的基本使用」:

https://codepen.io/PJCHENder/pen/dyGXZYb

Imgur

在完整的程式碼中,當使用者點擊箭頭時,數字可以正確更新,而且在瀏覽器 console 視窗中,你會看到每次資料一有更新,JSX 中的 console.log 就會再次被執行,表示畫面是有因為資料改變而連帶更新的。

useState 做了什麼#

簡單的說,透過 useState 我們建立了一個需要被監控的資料變數(count),而且透過它提供的 setCount 來改變 count 的數值時,React 會幫我們重新轉譯畫面,如此便解決了最上面提到的畫面不會更新的問題

現在我們回頭來看剛剛程式碼中的各個步驟。

取出 useState 方法#

useState 這個方法是放在 React 物件裡面的一個方法,所以要使用它的時候,可以使用 React.useState,或者可以透過物件的解構賦值(object destructuring assignment)來取出 useState 這個方法:

/* useState 是在 React 物件中的一個方法,取用它的方法主要有兩種 */
React.useState(); // 直接透過 `.` 來取用 React 物件內的方法
const { useState } = React; // 透過物件的解構賦值把 useState 方法取出

多數開發者以及 React 官方文件多是使用解構賦值的寫法,因此在後面的不同範例中也都會使用解構賦值的做法來載入 React Hooks。

提示

由於在 CodePen 的範例中已經有在 JavaScript Settings 透過 CDN 載入 React 套件,因此才能夠直接取用到 React 物件。

呼叫 useState 方法#

取出 useState 這個方法後,一旦我們呼叫了 useState 這個方法,它實際上會回傳一個陣列,這個陣列中的第一個元素會是我們「想要監控的資料」,第二個元素會是「修改該資料的方法」,像是這樣:

// useState() 的參數中可以帶入該資料的預設值,
// 同時呼叫後會回傳一個陣列
const arrayReturnFromUseState = useState('<資料預設值>');
// 陣列中的第一個元素是「想要監控的資料」
const count = arrayReturnFromUseState[0];
// 陣列中的第二個元素是「修改該資料的方法」
const setCount = arrayReturnFromUseState[1];

可是每次都要用 [0][1] 這樣的寫法實在是太麻煩了,所以大家也都會直接使用陣列的解構賦值(array destructuring assignment)來直接幫這個變數取名並賦值,像這樣:

const [count, setCount] = useState('<資料預設值>');
  • count 是透過 useState() 產生的變數,這是我們希望監控的變數,名稱可以自已取
  • setCount 則是 useState() 產生用來修改 count 這個資料的方法,名稱可以自己取
  • useState() 這個方法的參數中可以帶入資料的預設值

透過 useState 得到的變數和方法名稱是可以自己取的,而慣例上用來「改變變數的方法」名稱會以 set開頭;預設值也可以不一定要是字串或數值,而是可以帶入物件

下面這些例子都是合法的:

const [price, setPrice] = useState(1000);
const [description, setDescription] = useState('This is description');
const [product, setProduct] = useState({
name: 'iPhone 11',
price: 24900,
os: 'iOS',
});

React 畫面的重新轉譯#

上面我們有提到「透過 useState 建立了一個需要被監控的資料變數(count),並且透過 setCount 方法來改變 count 的數值時,React 會幫我們重新轉譯畫面」,這句話需要很仔細的來看。實際上 React 畫面之所以會更新並不是因為 count 的值改變了,而是因為:

  1. setCount 被呼叫到
  2. count 的值確實有改變

這兩個條件缺一不可。釐清這點相當重要,才不會覺得為什麼明明有呼叫 setCount 但畫面沒變,或 count 的值有變但畫面卻沒重新轉譯。

下面的程式碼都是沒有同時滿足上面這兩個條件,因此畫面不會更新的情況。

沒有使用 setCount 改變變數#

這其實是一種錯誤的寫法,既然已經用來 useState 來產生 count 這個變數,表示這個 count 應該是你認為要被 React 監控的資料,但這時候你卻沒有使用 setCount 來改變它,而是直接去改變 count 的值:

Imgur

使用了 setCount 但是 count 的值沒有改變#

這裡這樣寫只是為了示範當 count 的值沒變時,畫面並不會重新轉譯,但這不一定是錯誤的寫法。使用了 setCountcount 的值沒有變化時,React 會很聰明的不去做無意義的重新轉譯,因為資料根本就沒變,所以畫面也不需要更新,你可以從瀏覽器的 console 視窗中看到並沒有跳出 render 的訊息:

Imgur

React 只會更新畫面中有變化的部分#

最後,React 在更新畫面時,同樣會很聰明的只去更新有改變的部分,也就是說,它並不會把整個 DOM 都換掉,而是只換掉有變化的部分,也因此才能讓網頁運作的效能大大提升。

資料驅動畫面#

我們已經透過 React 來完成這個計數器。現在要請你試著回想一下,在沒有 React 之前,我們是怎麼去完成這個計數器的,當時我們需要先透過 querySelector 選擇 DOM 元素,接著使用 addEventListener 來綁定點擊事件,再來透過 textContent 的方式來改變畫面上的內容。

當我們畫面上只有一個計數器時這麼做並不會太過複雜,但若我們需要重複使用這個計數器的話,就會需要花比較多的工在做重複的事情了。

在我們換成 React 之後,除了透過元件的方式來整理可重複使用的元件外,透過 JSX 將 HTML 和 JavaScript 的操作進行了整合,我們只需要在 JSX 中使用 onClick 就可以把事件和事件處理器綁定上去,也可以直接在裡面使用變數。

其中有一個更重要的概念是,在 React 中是以「資料」來驅動畫面進行更新。我們不再需要透過 textContentinnerHTML 這樣的方式來更新 HTML 的畫面,因為現在畫面和元件的資料是可以關聯在一起的,因此一旦資料改變了,React 就會自動幫我們更新呈現的畫面。

未來我們會實作更多 React 的功能,你會發現到當我們要操控畫面時,不再是去想怎麼樣「改變畫面」,而是要去想怎麼樣「改變資料」,因為資料變動了,畫面自然就變動了。

換你了:練習看看 useState 的使用#

在這個單元中,我們已經完成了最基本的計數器功能,請你試著完成它,並把 count 的預設值改成 5。你也可以把 useState 回傳的狀態和方法(這裡取做 countsetCount)稍微改個名稱看看,相信你會對它的使用又更清楚的了解。

同樣的你可以到下方連結直接檢視完整的程式碼,或於 Github 專案說明頁點擊連結「useState 的基本使用」:

https://codepen.io/PJCHENder/pen/dyGXZYb

Imgur