# 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)