状态
现在我们知道了如何创建 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()
方法返回一个 Virtual DOM 树,其中包含文本“已单击”而不是“尚未单击”,从而导致 Preact 在 DOM 中更新按钮的文本。
使用 hook 在函数组件中设置状态
函数组件也可以有状态!虽然它们没有像类组件那样的 this.state
属性,但 Preact 附带了一个微小的附加模块,它提供了一些函数,用于存储和处理函数组件内部的状态,称为“hook”。
Hook 是可以在函数组件内调用的特殊函数。它们很特别,因为它们会记住跨渲染的信息,有点像类上的属性和方法。例如,useState
hook 返回一个包含一个值和一个“setter”函数的数组,可以调用该函数来更新该值。当一个组件被调用(重新渲染)多次时,它所做的任何 useState()
调用每次都会返回完全相同的数组。
ℹ️ hook 实际上是如何工作的?
在幕后,像
setState
这样的 hook 函数通过将数据存储在与 Virtual 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 将包含文本“Clicked”,而不是“No clicks yet”。这将导致 Preact 更新 DOM 中按钮的文本。
试一试!
让我们尝试创建一个计数器,从我们在上一章中编写的代码开始。我们需要在状态中存储一个 count
数字,并在单击按钮时将其值增加 1
。
由于我们在上一章中使用了函数组件,因此使用钩子可能最简单,不过你可以选择任何你喜欢的存储状态的方法。