定义 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 需要重新渲染组件本身。

Tip

示例中的语句const [count, setCount] = useState<number>(0);采用 ES6 中的解构赋值语法,具体的使用可以参考相应的资料。useState()函数可以接受一个泛型参数来定义State变量的类型。

如果在函数组件中需要使用多个state变量,只需要调用多次useState()来创建变量即可,或者直接使用useState()来创建一个对象或者数组变量。需要注意的是 State Hook 返回的函数setXXX()总是采用替换的方式修改变量内容,而不是合并更新。

Tip

如果你使用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 的操作。

Caution

位于startTransition中的State更新是可以被其他的状态更新打断的,在startTransition之外的State更新拥有更高的优先级被处理。所以Transition不能被用来控制文本的输入,即配合State组建受控组件