狀態
現在我們知道如何建立 HTML 元素和元件,以及如何使用 JSX 將屬性和事件處理常式傳遞給兩者,是時候學習如何更新虛擬 DOM 樹了。
正如我們在前一章中提到的,函數和類別元件都可以有狀態 - 由元件儲存的資料,用於變更其虛擬 DOM 樹。當元件更新其狀態時,Preact 會使用更新的狀態值重新渲染該元件。對於函數元件,這表示 Preact 將重新呼叫函數,而對於類別元件,它只會重新呼叫類別的 render()
方法。讓我們來看每個範例。
類別元件中的狀態
類別元件有一個 state
屬性,它是一個物件,包含元件在呼叫其 render()
方法時可以使用資料。元件可以呼叫 this.setState()
來更新其 state
屬性,並要求 Preact 重新渲染它。
class MyButton extends Component {
state = { clicked: false }
handleClick = () => {
this.setState({ clicked: true })
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.clicked ? 'Clicked' : 'No clicks yet'}
</button>
)
}
}
按一下按鈕會呼叫 this.setState()
,這會導致 Preact 再次呼叫類別的 render()
方法。現在 this.state.clicked
為 true
,render()
方法會傳回一個虛擬 DOM 樹,其中包含文字「已按一下」而不是「尚未按一下」,導致 Preact 更新 DOM 中按鈕的文字。
使用 Hooks 在函數元件中的狀態
函數元件也可以有狀態!雖然它們沒有像類別元件那樣的 this.state
屬性,但 Preact 附帶了一個微小的附加模組,它提供用於在函數元件內儲存和處理狀態的函數,稱為「Hooks」。
Hooks 是可以在函數元件內呼叫的特殊函數。它們很特別,因為它們記住跨渲染的資訊,有點像類別上的屬性和方法。例如,useState
Hook 會傳回一個包含值和「setter」函數的陣列,可以呼叫該函數來更新該值。當元件被呼叫(重新渲染)多次時,它所做的任何 useState()
呼叫每次都會傳回完全相同的陣列。
ℹ️ Hooks 實際上是如何運作的?
在幕後,像
setState
這樣的 Hook 函式透過將資料儲存在與虛擬 DOM 樹中每個元件相關聯的一連串「插槽」中來運作。呼叫 Hook 函式會用掉一個插槽,並遞增一個內部的「插槽編號」計數器,因此下一個呼叫會使用下一個插槽。Preact 在呼叫每個元件之前會重設此計數器,因此當一個元件被多次渲染時,每個 Hook 呼叫都會與同一個插槽相關聯。function User() { const [name, setName] = useState("Bob") // slot 0 const [age, setAge] = useState(42) // slot 1 const [online, setOnline] = useState(true) // slot 2 }
這稱為呼叫位置排序,這就是為什麼 Hook 必須在元件內部始終以相同的順序呼叫,且無法有條件呼叫或在迴圈內呼叫的原因。
讓我們來看看 useState
Hook 的實際範例
import { useState } from 'preact/hooks'
const MyButton = () => {
const [clicked, setClicked] = useState(false)
const handleClick = () => {
setClicked(true)
}
return (
<button onClick={handleClick}>
{clicked ? 'Clicked' : 'No clicks yet'}
</button>
)
}
按一下按鈕會呼叫 setClicked(true)
,這會更新由我們的 useState()
呼叫所建立的狀態欄位,進而導致 Preact 重新渲染此元件。當元件被渲染(呼叫)第二次時,clicked
狀態欄位的值會是 true
,而回傳的虛擬 DOM 會有文字「已按一下」而不是「尚未按一下」。這會導致 Preact 更新 DOM 中按鈕的文字。
試試看!
讓我們試著建立一個計數器,從我們在上一章寫的程式碼開始。我們需要在狀態中儲存一個 count
數字,並在按一下按鈕時將其值遞增 1
。
由於我們在上一章使用了函式元件,因此使用 Hook 可能最容易,不過你可以選擇你偏好的儲存狀態方法。