跳到主要内容

State 组件的记忆

  • 组件通常需要根据交互更改屏幕上显示的内容。
  • 输入表单应该更新输入字段,
  • 单击轮播图上的“下一个”应该更改显示的图片,
  • 单击“购买”应该将商品放入购物车。
  • 组件需要“记住”某些东西:当前输入值、当前图片、购物车。
  • 在 React 中,这种组件特有的记忆被称为 state

useState Hook

useState Hook 提供了这两个功能:

  • State 变量 用于保存渲染间的数据。
  • State setter 函数 更新变量并触发 React 再次渲染组件。
import { useState } from 'react'
const [index, setIndex] = useState(0)
// index 是一个 state 变量,setIndex 是对应的 setter 函数。

这里的 [ 和 ] 语法称为数组解构,它允许你从数组中读取值。 useState 返回的数组总是正好有两项。

  • Hook 是特殊的函数,只在 React 渲染时有效(我们将在下一节详细介绍)。
  • 它们能让你 “hook” 到不同的 React 特性中去。
  • Hooks ——以 use 开头的函数——只能在组件或自定义 Hook 的最顶层调用
  • 你不能在条件语句、循环语句或其他嵌套函数内调用 Hook。
  • Hook 是函数,但将它们视为关于组件需求的无条件声明会很有帮助。
  • 在组件顶部 “use” React 特性,类似于在文件顶部“导入”模块。
渲染过程
const [index, setIndex] = useState(0)
  1. 组件进行第一次渲染。 因为你将 0 作为 index 的初始值传递给 useState ,它将返回 [0, setIndex]。 React 记住 0 是最新的 state 值。
  2. 你更新了 state 。当用户点击按钮时,它会调用 setIndex(index + 1)index0,所以它是 setIndex(1)。这告诉 React 现在记住 index1 并触发下一次渲染。
  3. 组件进行第二次渲染。React 仍然看到 useState(0),但是因为 React 记住 了你将 index 设置为了 1,它将返回 [1, setIndex]
  4. 以此类推!

state 变量

  • 如果它们不相关,那么存在多个 state 变量是一个好主意,
  • 如果你发现经常同时更改两个 state 变量,那么最好将它们合并为一个。
  • 例如,如果你有一个包含多个字段的表单,那么有一个值为对象的 state 变量比每个字段对应一个 state 变量更方便。

构建 state 的原则

  1. 合并关联的 state。如果你总是同时更新两个或更多的 state 变量,请考虑将它们合并为一个单独的 state 变量。
  2. 避免互相矛盾的 state。当 state 结构中存在多个相互矛盾或“不一致”的 state 时,你就可能为此会留下隐患。应尽量避免这种情况。
  3. 避免冗余的 state。如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应将这些信息放入该组件的 state 中。
  4. 避免重复的 state。当同一数据在多个 state 变量之间或在多个嵌套对象中重复时,这会很难保持它们同步。应尽可能减少重复。
  5. 避免深度嵌套的 state。深度分层的 state 更新起来不是很方便。如果可能的话,最好以扁平化方式构建 state。

“让你的状态尽可能简单,但不要过于简单。”

import { useState } from 'react'

export default function MovingDot() {
const [position, setPosition] = useState({
x: 0,
y: 0,
})
return (
<div
onPointerMove={(e) => {
setPosition({
x: e.clientX,
y: e.clientY,
})
}}
style={{
position: 'relative',
width: '100vw',
height: '100vh',
}}>
<div
style={{
position: 'absolute',
backgroundColor: 'red',
borderRadius: '50%',
transform: `translate(${position.x}px, ${position.y}px)`,
left: -10,
top: -10,
width: 20,
height: 20,
}}
/>
</div>
)
}

不要在 state 中镜像 props

不要这样做
function Message({ messageColor }) {
const [color, setColor] = useState(messageColor);
  • 这里,一个 color state 变量被初始化为 messageColor 的 prop 值。
  • 这段代码的问题在于,如果父组件稍后传递不同的 messageColor 值(例如,将其从 'blue' 更改为 'red'),则 color state 变量将不会更新! state 仅在第一次渲染期间初始化。

这就是为什么在 state 变量中,“镜像”一些 prop 属性会导致混淆的原因。相反,你要在代码中直接使用 messageColor 属性。如果你想给它起一个更短的名称,请使用常量:

如果你想给它起一个更短的名称,请使用常量
function Message({ messageColor }) {
const color = messageColor;
// 这种写法就不会与从父组件传递的属性失去同步。
  • 只有当你 想要 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。
  • 按照惯例,prop 名称以 initial 或 default 开头,以阐明该 prop 的新值将被忽略:
function Message({ initialColor }) {
// 这个 `color` state 变量用于保存 `initialColor` 的 **初始值**。
// 对于 `initialColor` 属性的进一步更改将被忽略。
const [color, setColor] = useState(initialColor);