定义 State
State 在 React 中也是一个非常重要的概念,它存储着一个组件当前的状态,也可以看作一个组件的记忆。props
向组件传递的是组件的初始状态。在应用运行过程中,随着时间的推移和用户的操作,组件的状态是会发生改变的,但是由于 React 中props
不可更改的规约限制,使得我们需要另寻一种形式来控制组件的状态。所以 React 提供了 State 来支持这项需求。
在函数组件中,State 的定义是通过 State Hook useState
来完成的。State Hook 可以允许在函数组件中定义 State 及其变化函数。
import { useState } from "react";
function Counter(props) {
const [count, setCount] = useState<number>(0);
return (
<div>
<h1>Hello, {props.user}.</h1>
<div>Current count: {count}.</div>
<input type="button" onClick={() => setCount(count + 1)} />
</div>
);
}
函数useState()
可以使函数组件中存储内部state
。通过调用useState()
,可以声明一个 State 变量并赋予初始值,还可以给定一个用于改变这个 State 的函数。useState()
返回的 State 变量只是一个普通的变量,如果对其进行赋值操作,不会有任何效果,如果需要改变 State 变量的值,就必须使用useState()
返回的setXXX()
函数。在调用setXXX()
的时候,实际上除了更新 State 变量的值以外,还同时通知了 React 需要重新渲染组件本身。
示例中的语句const [count, setCount] = useState<number>(0);
采用 ES6 中的解构赋值语法,具体的使用可以参考相应的资料。useState()
函数可以接受一个泛型参数来定义State变量的类型。
如果在函数组件中需要使用多个state
变量,只需要调用多次useState()
来创建变量即可,或者直接使用useState()
来创建一个对象或者数组变量。需要注意的是 State Hook 返回的函数setXXX()
总是采用替换的方式修改变量内容,而不是合并更新。
如果你使用useState()
里声明了一个对象,那么在更新这个对象中的某一个或者某几个属性时,可以利用Javascript中的展开语法拼合新的对象,例如setPerson({...person, name: 'John'})
。当然你在使用这个语法的时候,还是需要注意这个语法浅拷贝的特点的。
一定要记得,State中保存的东西始终都应该是新的,无论其中保存的是对象还是数组。
当需要惰性创建一个非常昂贵的对象时,可以向 useState()
中传递一个函数,React 只会在首次渲染时调用这个函数,并不会在组件重新渲染时发生变化。针对这一点,需要与后文介绍的 useMemo()
的功能区别开来。
为什么不使用普通变量
在函数中使用普通变量是一件非常平常的事情,但是在函数组件中,为什么不能使用普通变量来存储组件的状态而是需要使用 State 呢?这是因为在函数组件中函数的局部变量无法触发组件的渲染,也无法在组件的多次渲染中持久保存数据。
在没有做特殊声明和处理的时候,React 是无法监视一个函数中局部变量的变化的,而且在正常情况下,一个函数在执行结束以后, 其中的局部变量就因为超出作用域而被释放了,所以只靠局部变量是无法在函数组件中完成状态保持这个任务的。
构建 State 的原则
理论上来说一个组件里可以定义无数个 State,但是数量更多的 State 只会带来更加复杂的管理逻辑。所以在实际逻辑中,State 的定义应该参考借鉴以下原则。
- 合并关联的 State,如果几个 State 总是同时更新,那么将其合并成一个 State 是一个更加合理的选择。
- 避免定义互斥的 State,互斥的 State 往往需要成套的范式代码来维护,一旦忘记更新其中的一个 State,那么可能就会引入不必要的 Bug。
- 避免定义冗余的 State,State 的更新操作会引起组件的重新渲染,如果一个 State 定义了但是并没有实际使用,可能会在无意中增加不必要的重新渲染动作。
- 避免定义重复的 State,重复的 State 很难保证其中内容的同步,在这种情况下,
useMemo()
可能是更好的选择。 - 避免定义深度嵌套的 State,如果你有足够的耐心来操作深度嵌套的 State,这一条你可以忽略。
如何不阻塞 UI 更新 State
State 的更新往往会带来组件的重新渲染,连续更新大量的 State 还可能会使应用的 UI 卡住。为了对这种情况进行优化,React 提供了一个useTransition
Hook,可以将一些状态更新操作标记为非阻塞的。
useTransition
会返回一个数组,使用时通常是这种格式:const [isPending, startTransition] = usetransition()
。startTransition
接受一个同步函数作为参数,在这个函数中可以完成更新 State 的操作。
位于startTransition
中的State更新是可以被其他的状态更新打断的,在startTransition
之外的State更新拥有更高的优先级被处理。所以Transition不能被用来控制文本的输入,即配合State组建受控组件。