# React.js Hook 入門 - 2. useState ###### tags: `React` `Hook` `TypeScript` ### 簡介 > 在我個人的經驗中,useState 算是使用最頻繁的 Hook。 在 class component 的寫法裡面,畫面 render 是依據 state 的改變。直到 Hook 出現之前,function component 因為不能使用 state,當需要使用時就必須將它改寫成 class component,往往令人覺得麻煩。 ### state 是什麼 我們在寫程式的時候,有時候會因為邏輯上的需要,宣告一些變數來做運算跟儲存結果。在 React 的世界裡面,一般宣告的變數,不會因為值的改變造成畫面重新渲染,但畫面的重新渲染卻可能會造成變數值的改變。為了因應這類的問題,React 使用 state 來解決這樣的問題。 State 只能在 componenet 內部使用,當 state 改變時,會觸發畫面重新渲染。使用 state 時需要仔細的考慮這個特性,若你需要一個變數,但卻不需要因為它而重新渲染畫面,此時你可以使用普通的變數,或是透過 useRef 來宣告變數,這方面需要依照使用情境來做選擇。 ### useState 的用法 #### 範例 1 ```typescript= import React, { useState } from 'react' function Counter() { // 宣告一個新的 state 變數,我們稱作為「count」。 const [count, setCount] = useState<number>(0) } ``` 第5行是 useState 的使用方式,重點如下。 1. useState 傳入的參數是 state 的初始值,若不給則會是 undefined。 2. 當呼叫 useState 時會回傳一個陣列,陣列的第一個元素為宣告的 state 變數,陣列的第二個元素則為更新 state 的 function。 3. 這邊在宣告 state 時使用了 array destructuring,若不熟悉可以參考下方 Reference。 #### 範例 2 ```typescript= import React, { useState } from 'react' function Counter() { const [count, setCount] = useState(0) return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ) } ``` 1. 第一行:從 React 引入 useState Hook。它讓我們可以在 function component 保留 local state。 2. 第四行:在 Counter component 裡,呼叫 useState Hook 宣告了一個新的 state 變數。並回傳了一對由我們命名的值。我們將代表點擊數的變數命名為 count。我們將起始值設為 0 並傳入 useState 當作唯一參數。第二個回傳的值是個可以更新 count 的 function,所以我們命名為 setCount。 3. 第九行:當按鈕點擊時,呼叫 setCount 並傳入新的值。然後 React 就會 re-render Example component,並傳入新的 count 值。 ### useState 的特性 1. State update function 是「非同步」執行的 前面有提到,當 state 改變時會觸發畫面重新渲染,但在短時間內有多個 state 改變時,React 有自己的一套控制機制來優化渲染的效能。state 在更新時是以非同步的方式,因此要小心使用避免渲染的結果跟你預期的不一樣。 拿範例二來修改,加入一個 add function ```typescript= import React, { useState } from 'react' function Counter() { const [count, setCount] = useState(0) return ( <div> <p>You clicked {count} times</p> <button onClick={() => add()}>Click me</button> </div> ) function add():void { setCount(count + 1) console.log(count) } } ``` 從第四行可以看到 count 在宣告後的值是 0,當使用者按下 button 後,會呼叫 add function,function 內會執行 setCount,我們會預期 15 行 console.log 的結果是 1,但實際執行卻是0。 為了避免這樣的情況發生,可以將 add function 改寫如下 ```typescript= import React, { useState } from 'react' function Counter() { const [count, setCount] = useState(0) return ( <div> <p>You clicked {count} times</p> <button onClick={() => add()}>Click me</button> </div> ) function add():void { setCount(() => { const result = count + 1 console.log(result) return result }) } } ``` 2. Lazy initial state useState 的 initial value 只有在第一次宣告時有用,但是每次重新渲染時都還是會跑一次 initial value。若你的 initial value 是透過呼叫某個 function 得到回傳的結果,當每次畫面重新渲染時都會再次呼叫該 function,即使該 function已經沒有用了。 ```typescript= import React, { useState } from 'react' function Counter() { const [count, setCount] = useState(getInitCount()) function getInitCount():number { return 0 } } ``` 若要避免此種狀況發生,可以透過 Lazy initial state 來避免 ```typescript= import React, { useState } from 'react' function Counter() { const [count, setCount] = useState(() => getInitCount()) function getInitCount():number { return 0 } } ``` 3. State update function 有可能不會觸發重新渲染 畫面重新渲染的條件是 state 的改變,當你 update 的值跟原本的相同時則不會觸發畫面重新渲染。當你的 state 是個 object 時,由於 object 判斷相不相同看的是記憶體中的位置,若你只是透過一般修改 object 的屬性是不會觸發畫面重新渲染,這是 React 新手蠻常會忽略部分。 ##### Reference [Using the State Hook](https://reactjs.org/docs/hooks-state.html) [Array Destructuring](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#%E9%99%A3%E5%88%97%E8%A7%A3%E6%A7%8B) [關於 useState,你需要知道的事](https://medium.com/@xyz030206/%E9%97%9C%E6%96%BC-usestate-%E4%BD%A0%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84%E4%BA%8B-5c8c4cdda82c)