useState
為何需要 state
今天我們先使用一個普通的 Javascript 變數 number
作為狀態值,當點擊畫面上的按 鈕時,觸發事件時同樣會更新它的值:
//狀態變數
export default addButton = () =>{
const number = 0;
const addNumber = ()=>{number = number+1 }
return (
<>
<p> current {number} </p>
<button onClick={addNumber}></button>
</>
)
}
你會發現這個狀態值的變化無法被保存,因為每次重新渲染頁面時都會重新為 number
賦值。 react 提供的 useState
產生的狀態值,除了可以解決數值改變無法在頁面渲染間被保存的問題外,也將 state
改變與頁面渲染進行掛鉤。
使用限制
- 只能在 function component 使用 ,class component 不能
- 只能在 function component 內的 top-level 呼叫,不可在
block scoped
呼叫
而所謂的 block scoped 舉例來說就包括:
import {useState} from 'react'
export default LimitShow = ()=>{
useState() //可
if(true) { useState(1) } // 不可
for(xx) { useState(1) } // 不可
return <></>
}
- 更新 state 需遵守 immutable 原則
在 Javascript 中資料主要分為兩種類型, Primitive types 及 Reference types ,像是 Number
,String
,Boolean
等類型的資料,就是所謂的 Primitive types 的類型,而像是 Array
, Object
這類型的資料則為 Reference types 。
如何區分兩者?
當你使用 const
進行宣告時,Primitive types 的資料無法再重新賦值,這就符合所謂的 immutable(不可改變)
const x = 2;
x = 3; //error x is constant variable
const y = x; //ok!
反之,你仍可對 Reference types 的資料進行調整,或是拷貝,這就是所謂的 mutable(可改變)
const objX = {name:"andy"};
objX.name = "danny" // ok!
為何 react 會嚴格限制 state 需遵守 immutable 原則? 作者理解 react 是透過偵測狀態(state)記憶體位置改變,來決定是否要重新渲染,當你直接對 Reference types 的資料進行調整時,記憶體位置並沒有改變,會發生資料已經改變,但是畫面沒有更新的情形。
回傳值
useState
會回傳一個陣列 ,index: 0 是初始的 state
,index: 1 則是更新 state
的函式。
function LimitShow(){
const state = useState(1);
state = [1,handlerState()]
}
所以一般使用上會搭配解構賦值將 儲存狀態變數 跟 處理狀態變數函式 解構,方便各別使用。
const [state,setState] = useState(1);
如何更新 state ?
Primitive types
const [state,setState] = useState(1)
//更新方式
setState(val)
Reference types
直接更新 reference type 的資料內容(如下圖) ,在 react 無法被偵測到 state
有產生改變,畫面更新會失效。
const [test,setTest] = useState({name:'1'})
// x: 直接更新物件 key 值
setState(()=>test.name="2")
要保持 state 值維持 immutable
的情形去更新,有以下方法:
物件型別更新
離散語法
const [state,setState] = useState({x:1,y:2})
setState({...state,要更新部分的值})
存入全新物件
const [state,setState] = useState({x:1,y:2})
setState({x:2,y:3})
上面都是單層物件資料,如果今天是巢狀(nested)的狀態(如下),可以這樣思考去拆解
export default function nestObj(){
const [obj,setObj] =useState({
name:"andy",
interest:{ball:'basketball'}
})
}
- 把
interest
物件先單獨拉出來
const newInterest = {...obj.interest}
newInterest.ball = 'baseball'
- 再將更新的值存入本來的狀態
interest
物件
setObj({...obj,interest:newInterest})
所以其實就是單層的更新方法做兩次。
陣列型別更新
陣列如同物件是 mutable 的,且許多內建的陣列方法會修改原始陣列造成 mutable ,這邊我擷取了 react 官方文件內條列的比較表(如下),透過這張表可以快速知道,要避免直接使用哪些方法更新 state 。