agentmentoragentmentor

第 04 节:State 和事件:界面为什么会变

本节 objectives:

  • 能用 useState 保存组件内部会变化的数据。
  • 能用事件处理函数响应点击。
  • 能解释为什么直接改变量不会更新界面。

先修:props 和 JSX | 上一节 << 03 | 下一节 05 >>

用户点了一下,组件必须记住这一下

props 来自父组件,但很多变化发生在组件自己里面:按钮点了几次、面板是否展开、当前选中哪一项。React 官方文档把 state 称为组件的记忆,并用 useState 保存会随交互变化的数据。1

事件是变化的入口,state 是变化的记忆。

讲解

基础写法:

jsx
import { useState } from "react";
function Counter() {  const [count, setCount] = useState(0);
  function handleClick() {    setCount(count + 1);  }
  return <button onClick={handleClick}>点击 {count}</button>;}

count 是当前 state 值,setCount 是请求 React 更新它的函数。直接写 count = count + 1 不行,因为 React 不会因此知道要重新渲染组件。

React 的事件处理函数写在 JSX 属性上,例如 onClick={handleClick}。注意这里传的是函数本身,不是 handleClick() 的执行结果。官方事件文档也强调事件处理函数通常由你定义,再传给 JSX 中的事件属性。2

state 更新后,React 会重新调用组件函数,用新的 state 计算下一次 UI。

跟我做一遍(worked example)

需求:做一个可展开的课程说明。

jsx
import { useState } from "react";
function CourseSummary() {  const [open, setOpen] = useState(false);
  function toggleOpen() {    setOpen(!open);  }
  return (    <section>      <button onClick={toggleOpen}>        {open ? "收起说明" : "展开说明"}      </button>      {open && <p>这门课会带你做出一个小型 React 任务组件。</p>}    </section>  );}

为什么 open 要放 state:它不是父组件传来的固定数据,而是用户点击后改变的界面记忆。为什么用 setOpen:React 需要通过 setter 知道下一次渲染要用新值。

换你补全(faded example)

补全一个"喜欢"按钮。关键空白是更新 state 的方式,不是按钮文案。

jsx
import { useState } from "react";
function LikeButton() {  const [liked, setLiked] = useState(false);
  function handleClick() {    ________;  }
  return (    <button onClick={________}>      {liked ? "已喜欢" : "喜欢"}    </button>  );}

参考答案:

jsx
function handleClick() {  setLiked(!liked);}
return (  <button onClick={handleClick}>    {liked ? "已喜欢" : "喜欢"}  </button>);

常见错误是写 onClick={handleClick()}。那会在渲染时立刻调用函数,不是等用户点击。

小结 + 通向下一节

state 保存组件内部记忆,事件处理函数决定何时更新它。React 的界面变化不是手动改 DOM,而是 state 变化后重新计算 UI。

下一节把这个机制用到分支和数组上:不同 state 渲染不同界面,一组数据渲染一组元素。

Footnotes

  1. State: A Component's Memory — https://react.dev/learn/state-a-components-memory

  2. Responding to Events — https://react.dev/learn/responding-to-events

练习

Level 1: 写一个 StepCounter 组件,按钮每点一次,显示的步数加 1。

提示 1

先写一个会显示 state 的组件。

提示 2

再加一个只做一件事的事件处理函数。

提示 3

每次变化都通过 setter 发生。

自评