本篇筆記摘錄自官方文件 State: A Component’s Memory
本篇將會開啟 React 的超級大重點 - 狀態 ( state )。
元件常常會需要為了回應使用者互動而改變畫面顯示。
就像是使用者在輸入框輸入內容時,我們必須要讓使用者知道自己輸入了什麼,或是在幻燈片上點擊下一頁的時候,裡面的照片應該要跳到下一頁,或是點擊購買時,要將產品放入購物車內。
元件會需要去記住這些事情:當前輸入值是什麼?現在的照片是哪一張?購物車裡面有哪些內容?
元件的記憶,稱為狀態。
When a regular variable isn’t enough
下方範例為一個用來渲染雕像照片的元件。
預期的功能為:點擊 Next 按鍵時,透過index
從1
變為2
,以此類推來更新照片內容。
但目前的寫法是沒有辦法如預期運作的,可以透過右方的輸出結果測試。
( 將左側拉桿向右移動,可以看見程式碼的部分,拉開後點擊左上角三條線漢堡圖示可以看見完整檔案,右邊則為輸出結果。 )
handleClick
更新的是區域變數index
,無法如預期運作的原因如下:
- 區域變數不會在渲染間持續存在,當 React 渲染元件第二次的時候,它會重新渲染,並不會去管區域變數有什麼改變。
- 改變區域變數不會觸發渲染,React 不會知道它需要再次渲染元件裡的新資料。
如果想要更新元件的資料,必須有以下兩個條件:
- 渲染間須保持資料的維持。
- 觸發 React 用新資料渲染元件 ( 重新渲染 )。
而useState
Hook 滿足了以上兩個條件:
- 狀態變數可以在渲染間持續存在。
- 狀態設置函式 ( setter function ) 可以更新變數,並且觸發重新渲染。
Adding a state variable
首先,為了加入狀態變數,必須在檔案的最上方匯入useState
:
import { useState } from ‘react’;
接下來,將原本的程式碼
let index = 0;
修改為
const [index, setIndex] = useState(0);
index
為狀態變數,而setIndex
為用來設置的函式 ( setter function )。
這邊的[]
語法稱為陣列解構賦值 ( array destructuring ),這使我們可以從陣列中讀取值,使用useState
時,這兩個項目都必須存在。
接下來我們來修改事件監聽器的內容:
function handleClick() {
setIndex(index + 1);
}
使用setIndex
達成每次點擊時將index
+ 1。
點擊輸出結果的 Next 按鍵,可以發現已經成功做出我們想要的效果:
Meet your first Hook
在 React 中,所有以use
為命名開頭的函式皆被稱為 Hook,就像useState
。
Hooks 是一種特別的函式,它們只有在 React 渲染時可取得,使我們可以 “hook into” React 不同的功能,而狀態只是其中一種。
【 Pitfall 】
Hooks 只能在元件或是自定義 Hooks 中的最頂層被呼叫,不能在條件式、迴圈或其他巢狀函式中呼叫它。
Anatomy of useState
當我們呼叫useState
,就是在告訴 React,希望這個元件記住某些事情:
const [index, setIndex] = useState(0);
在這個範例裡,我們希望 React 記住index
。
命名慣例: const [something, setSomething]
雖然也可以根據個人喜好命名,但依照命名慣例命名,可以讓整個專案更加容易被理解,提升程式碼的閱讀性。
useState
中的唯一引數 ( argument ),就是狀態變數的初始值,在這個範例裡,我們將index
的初始值設為0
。
const [index, setIndex] = useState(0);
元件每一次渲染時,useState
都會給出一個包含兩個值的陣列:
- 我們所儲存的狀態變數。
- 可以更新狀態變數與觸發 React 再次渲染元件的設置函式。
以下為發生的順序:
- 元件第一次渲染的時候,由於我們將
0
傳入useState
當作index
的初始值,所以會 return[0, setIndex]
,React 會記住0
是最新的狀態變數 - 更新狀態:
當使用者點擊按鈕時,呼叫setIndex(index + 1)
。index
是0
,所以會變成setIndex(1)
,這會告訴 React,現在的index
是1
,並且觸發渲染。 - 元件第二次渲染,React 一樣會看見
useState(0)
,但是 React 會記得我們把index
設為1
了,因此會 return[1, setIndex]
。 - 以此類推。
Giving a component multiple state variables
在同一個元件中,可以有多個不同類型的狀態變數。
以下方這個例子來說,Gallery
元件擁有兩個狀態變數,數字類型的index
與布林值showMore
,請試著點擊 Show details 按鍵:
當狀態變數彼此互不相關,將它們分成不同的狀態變數是一個好的選擇,就像是範例中的index
與showMore
,但當你發現兩個狀態變數常常一起改變時,那就適合將它們合併。
State is isolated and private
狀態是元件裡的區域實例 ( local instance ),換句話來說,如果我們渲染了同樣的元件兩次,兩個狀態是各自獨立的,改變其中一個並不會影響到另一個。
在這個範例裡,Gallery
元件被渲染了兩次,但是點擊後可以發現,左側與右側的數字互不影響,互相獨立:
雖然我們渲染了兩次Gallery
,但它們的狀態是分開儲存的。
這個範例還有一個值得注意的地方,Page
元件不會知道Gallery
元件的狀態,甚至不會知道它有沒有設置狀態,跟 props 不同,狀態在元件裡的宣告是完全私有的,父元件無法改變子元件的狀態,這個特性使得我們可以將狀態加入或移出元件,而不必擔心影響到其他元件。
如果希望兩個Gallery
能夠同步它們的狀態,則需將狀態從子元件裡移除,並將狀態加到最靠近它們的共同父元件,使用方法將會在之後的筆記內提到,也可以先參照官方文件 Sharing State Between Components。