0%

已完成 0/29 关

教学模式

你可以随时调整。

此设置调整教师的教学方式,无需重新生成课程。

第 20 关 含代码 约 12 分钟

用 State 响应输入:从声明式视角重塑 UI 状态机

用 State 响应输入:从声明式视角重塑 UI 状态机

在传统的前端开发中,想要让页面对用户输入做出反应,我们通常会使用“命令式(Imperative)”的方式: 寻找按钮元素 ightarrow ightarrow 禁用按钮 ightarrow ightarrow 寻找加载图标 ightarrow ightarrow 显示加载图标 ightarrow ightarrow 发送网络请求 ightarrow ightarrow 收到报错 ightarrow ightarrow 寻找错误文本容器 ightarrow ightarrow 写入错误文字并重新启用按钮……

在代码较少时这似乎很直接,但在复杂界面中,命令式逻辑极其容易导致灾难性的“状态丢失”:例如网络请求失败了,但你忘记把按钮从“禁用”状态重新恢复,导致用户再也无法点击。

React 采用的是截然不同的 声明式(Declarative) 编程思维:你不用告诉计算机“怎么一步步去改 DOM”,你只需要告诉计算机“在特定状态下,界面应该长成什么样”。


1. 声明式 UI 开发五步法

面对任何交互界面,遵循以下标准五步,你会发现构建动态组件变得无比清晰:

第一步:定位组件的所有视觉状态(Identify your component’s different visual states)

在写任何 JavaScript 逻辑之前,先像设计师一样思考这个组件都有哪些模样:

  • Empty(空白态):输入框为空,“提交”按钮处于禁用状态;
  • Typing(输入态):输入框有字,“提交”按钮变为可用状态;
  • Submitting(提交中):输入框和按钮都被禁用,显示“正在提交…”的加载图标;
  • Success(成功态):表单完全隐藏,只显示“答对了!感谢参与”的大标题;
  • Error(错误态):输入框保持可编辑,下方出现一串醒目的红色报错文本。

第二步:确定是什么触发了状态变更(Determine what triggers those state changes)

状态的变迁只来自两类驱动源:

  1. 人类输入:点击按钮、输入文本、切换选择;
  2. 计算机事件:网络请求成功返回、网络超时、定时器倒计时结束。

第三步:使用 useState 建模状态(Represent the state in memory with useState)

尽量从最直观的几个状态开始:

const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', 或 'success'

第四步:消除非必要和矛盾的状态(Remove any non-essential state variables)

问自己几个问题:

  • 是否存在矛盾?比如如果你同时声明了 isSubmitting 和 isSuccess,可能会在代码里出现它们同时为 true 的荒谬情况。用单一状态值 status(取值为枚举值)就能彻底杜绝这种非法状态。
  • 能否根据现有数据算出来?比如“答案是否为空”,直接 answer.length === 0 即可,不要专门再加一个 isEmpty 状态!

第五步:连接事件处理函数以设置状态(Connect the event handlers to set state)

编写 handleSubmit 和 handleInputChange,在用户动作时调用 setStatus 或 setAnswer。


2. 完整实战:城市问答表单

让我们把这五步完整付诸实践:

import { useState } from 'react';

export default function Form() {
  const [answer, setAnswer] = useState('');
  const [error, setError] = useState(null);
  const [status, setStatus] = useState('typing'); // 'typing', 'submitting', 'success'

  if (status === 'success') {
    return <h1>答对了!你是地理小天才!</h1>;
  }

  async function handleSubmit(e) {
    e.preventDefault();
    setStatus('submitting');
    try {
      await submitForm(answer);
      setStatus('success');
    } catch (err) {
      setStatus('typing');
      setError(err);
    }
  }

  function handleTextareaChange(e) {
    setAnswer(e.target.value);
  }

  return (
    <>
      <h2>城市知识竞答</h2>
      <p>世界上哪座城市拥有能把空气直接变成饮用水的广告牌?</p>
      <form onSubmit={handleSubmit}>
        <textarea
          value={answer}
          onChange={handleTextareaChange}
          disabled={status === 'submitting'}
        />
        <br />
        <button disabled={
          answer.length === 0 ||
          status === 'submitting'
        }>
          提交答案
        </button>
        {error !== null && (
          <p style={{ color: 'red' }}>{error.message}</p>
        )}
      </form>
    </>
  );
}

function submitForm(answer) {
  // 模拟异步网络验证接口
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      let shouldError = answer.toLowerCase() !== '利马';
      if (shouldError) {
        reject(new Error('猜错了,再试试看吧!(提示:秘鲁首都)'));
      } else {
        resolve();
      }
    }, 1500);
  });
}

总结

  • 告别命令式直接操作 DOM 的旧思维,拥抱声明式 UI;
  • 像状态机一样思考,先穷举组件可能的所有视觉状态;
  • 用 useState 描述当前所处的状态,杜绝相互矛盾的非法状态;
  • 页面标签自动根据当前状态声明性地呈现对应外观。
下一站:选择 State 结构:分组、避免矛盾与消除冗余 →
© 2026 React 官方教程