React 18 用 State 响应输入

1 声明式 UI命令式 UI

一、核心思想对比

维度 命令式 UI 声明式 UI
关注重点 怎么做」------ 需手动编写每一步操作的具体逻辑,明确指挥 UI "如何变化"。 是什么」------ 只需声明 UI "最终应该是什么样",由框架 / 引擎自动处理 "如何变化" 的细节。

二、结合表单案例的实现方式对比

以文中 "用户提交答案的表单" 交互为例:

  • 命令式 UI 的实现逻辑(需手动控制每一步):

    1. 监听 "表单输入" 事件 → 输入有内容时,手动设置 "提交按钮" 为可用状态;
    2. 监听 "提交按钮点击" 事件 → 点击后,手动设置 "表单" 和 "提交按钮" 为不可用,并手动启动 "加载动画";
    3. 网络请求成功 → 手动隐藏 "表单",手动显示 "提交成功" 信息;
    4. 网络请求失败 → 手动显示 "错误信息",手动将 "表单" 设为可用状态。
  • 声明式 UI 的实现逻辑 (只需声明状态与 UI 的映射关系):定义几个核心状态(如 isFormEnabledisLoadingisSuccessisError),然后声明 "状态→UI" 的映射规则

    • isFormEnabled=false → 表单和提交按钮不可用;
    • isLoading=true → 显示加载动画;
    • isSuccess=true → 隐藏表单,显示 "提交成功";
    • isError=true → 显示错误信息,且 isFormEnabled=true(表单重新可用)。当用户操作或网络请求改变这些状态时,框架会自动更新 UI,无需手动编写每一步 DOM 操作。

三、优缺点对比

类型 优点 缺点
命令式 UI 对 "每一步操作" 的控制力极强,适合需要精细定制的场景。 代码冗余度高(相同交互需重复写操作逻辑)、易出错(手动操作 DOM 容易遗漏边缘情况)、维护成本高(需求变更时需修改多处逻辑)。
声明式 UI 代码更简洁、易维护(只需关注 "状态和 UI 映射");框架自动处理性能优化(如 diff 算法减少无效渲染);开发效率高。 对 "底层操作细节" 的控制力弱,特殊定制场景可能需要结合命令式逻辑补充。

典型技术栈举例

  • 命令式 UI 代表:jQuery (需手动写 $('button').attr('disabled', true) 这类命令式操作)。
  • 声明式 UI 代表:React、Vue、Flutter(通过组件和状态管理,声明式描述 UI 应该是什么样)。

简单来说,命令式是 "手把手教电脑做事",声明式是 "告诉电脑要什么结果,让它自己想办法实现"。

前者灵活但繁琐,后者高效但对框架依赖度高,这也是现代前端框架普遍采用声明式思路的核心原因。

2 触发 state 的更新来响应输入

一、表单的 "状态机" 模型(对应状态图)

图中的每个圆形代表表单的一个状态 ,箭头代表状态切换的触发条件

  • Empty(空值状态):表单输入框无内容,提交按钮不可用。
  • Typing(输入中状态):用户开始输入内容,触发 "Start typing" 条件,从 Empty 切换到 Typing。
  • Submitting(提交中状态):用户点击提交按钮,触发 "Press submit" 条件,从 Typing 切换到 Submitting。
  • Error(失败状态):提交时网络请求失败,触发 "Network error" 条件,从 Submitting 切换到 Error。
  • Success(成功状态):提交时网络请求成功,触发 "Network success" 条件,从 Submitting 切换到 Success。

二、"状态→UI" 的驱动逻辑(如何用 state 更新 UI)

在声明式 UI 开发中,状态(state)是 UI 变化的核心驱动 。针对这个表单,需要定义以下 state 变量和切换逻辑:

  1. 输入框文本变化 → 状态切换(Empty ↔ Typing)

    • 定义 inputValue(记录输入框内容)和 formState记录当前表单状态,如 'empty' | 'typing' | 'submitting' | 'error' | 'success')。
    • 监听输入框的 "文本变化" 事件:
      • inputValue 从空变为非空 → formState'empty' 切换到 'typing',同时提交按钮变为可用。
      • inputValue 从非空变为空 → formState'typing' 切换回 'empty',提交按钮变为不可用。
  2. 点击提交按钮 → 状态切换(Typing → Submitting)

    • 定义点击事件处理函数,点击时将 formState 设为 'submitting',此时表单和提交按钮变为不可用,同时显示加载动画。
  3. 网络请求结果 → 状态切换(Submitting → Error / Success)

    • 网络请求成功:将 formState 设为 'success',隐藏表单并显示 "提交成功" 提示。
    • 网络请求失败:将 formState 设为 'error',显示错误信息并将表单恢复为可用状态(方便用户重新提交)。

三、"事件处理函数" 的作用(人为输入的响应)

"人为输入"(如打字、点击按钮)需要通过事件处理函数来捕获,并触发状态变化:

  • 输入框的 "onChange" 事件处理函数:监听文本变化,更新 inputValue 并切换 formState
  • 提交按钮的 "onClick" 事件处理函数:监听点击操作,将 formState 切换为 'submitting'

四、状态可视化的价值

在写代码前画出 "状态图"(如文中的圆形和箭头),可以提前梳理清楚所有状态和切换逻辑,避免开发时遗漏边缘情况(比如网络失败后表单要恢复可用、输入清空后状态要回退等),从而减少 bug。

总结来说,这段内容是在解释声明式 UI 中 "状态驱动 UI" 的核心逻辑------ 通过定义清晰的状态和状态切换规则,让 UI 自动跟随状态变化,而开发者只需关注 "状态是什么" 和 "如何触发状态变化",无需手动操作每一步 UI 变更。

3 Reducer 减少 "不可能" state

一、核心概念:Reducer 用于状态管理的价值

Reducer 是一种用于集中管理状态逻辑 的模式,常用于 React 等框架的状态管理(如 Redux、React Reducer Hook)。在表单这类多状态场景中,它的核心作用是避免 "无效状态组合",让状态变化更可控。

二、为何用 Reducer 减少 "不可能" state?

以文中表单为例,若用多个独立状态变量(如 errorstatusinputValue),可能出现逻辑矛盾的状态组合(比如 status=successerror 却非空)。而 Reducer 可以:

  1. 合并多状态为一个对象 :将 errorstatusinputValue 等状态封装到一个统一的状态对象中。
  2. 固化状态变化逻辑:通过 "动作(Action)→ reducer 函数 → 新状态" 的流程,确保每次状态变化都遵循预设规则,从而避免出现无意义的中间状态。

三、实际作用

  • 让状态逻辑更模块化:所有状态变化的逻辑都集中在 reducer 函数中,便于维护和调试。
  • 提升代码健壮性:从根源上避免因状态组合混乱导致的 bug,让表单的状态切换始终符合业务逻辑(如 "成功时必然无错误""提交中时必然不可编辑" 等)。

4 state 响应输入摘要

一、声明式编程 vs 命令式编程(UI 开发视角)

  • 命令式编程 :需要开发者细致地控制 UI 的每一步变化。比如要实现一个按钮点击后文字变化,你得手动找到这个按钮元素,然后修改它的文本内容,像 "获取按钮 DOM → 绑定点击事件 → 事件里修改按钮文本",每一步都要明确指挥。
  • 声明式编程 :只需声明 "每个视图状态对应的 UI 长什么样",不用关心 UI 如何变化的细节。以 React 为例,你只需描述 "状态 A 时按钮显示'点击我',状态 B 时显示'已点击'",框架会自动帮你处理从状态 A 到 B 时 UI 的更新逻辑。

二、开发组件时的状态管理步骤(以 React 为例,useState 是其 Hooks 之一,用于管理组件状态)

1. 写出组件中所有的视图状态

视图状态是影响 UI 显示的变量 。比如做一个 "点赞按钮" 组件,状态可能有:是否已点赞(决定按钮显示 "点赞" 还是 "已点赞")、点赞数(显示具体数字)。

2. 确定是什么触发了这些 state 的改变

即找到状态的 "驱动因素"。比如 "是否已点赞" 由用户点击按钮 触发;"点赞数"由用户点击(点赞或取消点赞) 触发。

3. 通过 useState 模块化内存中的 state

useState 是 React 中把状态 "封装" 到组件内存里的方式,让状态可管理、可更新。语法是 const [状态变量, 状态更新函数] = useState(初始值)。以点赞组件为例:

javascript 复制代码
const [isLiked, setIsLiked] = useState(false); // 初始未点赞
const [likeCount, setLikeCount] = useState(0); // 初始点赞数为 0
4. 删除任何不必要的 state 变量

只保留直接影响 UI 且无法通过其他状态推导的变量。比如如果 "点赞数" 能通过 "是否已点赞"+ 全局数据计算得到,且不需要实时本地维护,就可以删除这个 state,避免冗余。

5. 连接事件处理函数去设置 state

把用户操作(如点击)和状态更新函数绑定,让操作能触发状态变化,进而驱动 UI 更新。还是以点赞组件为例:

javascript 复制代码
const handleClick = () => {
  if (isLiked) {
    setIsLiked(false);
    setLikeCount(likeCount - 1);
  } else {
    setIsLiked(true);
    setLikeCount(likeCount + 1);
  }
};

// 按钮组件中绑定事件
<button onClick={handleClick}>
  {isLiked ? '已点赞' : '点赞'}
  {likeCount}
</button>

总结来说,这段内容核心是讲解声明式 UI 开发的理念 ,以及在 React 这类框架中如何通过明确的步骤管理组件状态,让 UI 与状态的映射更清晰、代码更易维护。

5 案例:React 中通过状态管理实现 CSS 类的动态切换

一、挑战题目详情

需求:实现一个图片交互效果,具体如下:

  1. 初始状态:外部 <div> 包含 background--active 类(视觉上显示紫色背景),内部 <img> 只有 picture 类(无高亮边框)。
  2. 点击图片时:
    • 移除 <div>background--active 类(紫色背景消失)。
    • <img> 添加 picture--active 类(图片边框高亮)。
  3. 再次点击图片时:恢复初始状态(背景重新高亮,图片边框高亮消失)。

初始代码

javascript 复制代码
export default function Picture() {
  return (
    <div className="background background--active">
      <img
        className="picture"
        alt="Rainbow houses in Kampung Pelangi, Indonesia"
        src="https://i.imgur.com/5qwVYb1.jpeg"
      />
    </div>
  );
}

二、我的思考与疑问(学习过程记录)

拿到题目后,我的第一反应是 "需要写一个函数响应点击事件",但具体怎么实现样式切换却有点模糊。当时的想法是:

  • 点击图片时,应该触发一个函数,在函数里直接修改 DOM 元素的 class(比如用 classList.addremove)。
  • 但又想到 React 好像不推荐直接操作 DOM,而且 "再次点击恢复原状" 的逻辑需要记录当前状态,单纯修改 DOM 无法记住之前的操作。
  • 所以产生了疑问:"如何用 React 的方式实现这种'点击切换'的效果?是不是我没领会响应式的写法?"

三、知识点梳理(结合解答总结)

通过学习解答,我理解了 React 中处理这类交互的核心逻辑,主要包含以下知识点:

1. React 响应式的核心:状态驱动 UI

  • 声明式编程理念:React 中,UI 是 "声明" 出来的,即 UI 的样子由当前状态决定,而不是通过命令式代码一步步控制 DOM 变化。
  • 状态(state)的作用:用于记录组件的动态信息(比如 "是否点击过图片"),状态变化时,React 会自动重新渲染组件,让 UI 与新状态保持一致。
  • 事件处理的角色:用户操作(如点击)会触发事件处理函数,函数的作用是 "修改状态",而不是直接修改 UI。

2. 实现步骤拆解

步骤 1:定义状态记录交互状态

需要一个状态记录图片是否被激活(点击),这里用布尔类型的 isImageActive

  • isImageActive = false:未激活(初始状态,背景有 background--active,图片无 picture--active)。
  • isImageActive = true:已激活(点击后状态,背景无 background--active,图片有 picture--active)。

通过 useState 定义状态:

javascript 复制代码
import { useState } from 'react';

export default function Picture() {
  // 初始化状态为 false(未激活)
  const [isImageActive, setIsImageActive] = useState(false);
  // ...
}
步骤 2:编写事件处理函数修改状态

点击图片时,需要切换状态(falsetruetruefalse),因此事件处理函数的逻辑是 "反转当前状态":

javascript 复制代码
const handleImageClick = () => {
  // 用 setIsImageActive 修改状态,传入"当前状态的反值"
  setIsImageActive(!isImageActive);
};
步骤 3:根据状态动态设置 CSS 类

通过模板字符串和三元表达式,根据 isImageActive 的值动态拼接 className

  • 外部 <div>:未激活(isImageActive = false)时加 background--active,激活时不加。
  • 内部 <img>:激活(isImageActive = true)时加 picture--active,未激活时不加。

代码实现:

javascript 复制代码
<div className={`background ${!isImageActive ? 'background--active' : ''}`}>
  <img
    className={`picture ${isImageActive ? 'picture--active' : ''}`}
    alt="Rainbow houses..."
    src="https://i.imgur.com/5qwVYb1.jpeg"
    onClick={handleImageClick} // 绑定点击事件
  />
</div>

四、完整解决方案代码

javascript 复制代码
import { useState } from 'react';

export default function Picture() {
  // 状态:记录图片是否被激活(初始未激活)
  const [isImageActive, setIsImageActive] = useState(false);

  // 点击事件处理函数:切换状态
  const handleImageClick = () => {
    setIsImageActive(!isImageActive);
  };

  return (
    // 外部div的class根据状态动态变化
    <div className={`background ${!isImageActive ? 'background--active' : ''}`}>
      <img
        // 图片的class根据状态动态变化
        className={`picture ${isImageActive ? 'picture--active' : ''}`}
        alt="Rainbow houses in Kampung Pelangi, Indonesia"
        src="https://i.imgur.com/5qwVYb1.jpeg"
        onClick={handleImageClick} // 绑定点击事件
      />
    </div>
  );
}

五、关键知识点对比与总结

错误思路(命令式) 正确思路(React 声明式)
直接用 document.querySelector 操作 DOM 的 classList useState 定义状态,通过状态控制 className
事件处理函数直接修改 UI 事件处理函数只修改状态,UI 由状态自动驱动
无法记录历史状态(难以实现 "再次点击恢复") 状态会记录当前状态,通过反转状态实现切换

核心结论:React 中实现交互效果的关键是 "用状态记录变化,用状态驱动 UI",事件处理函数的作用是 "修改状态",而非直接操作 DOM。这种方式让代码更符合声明式理念,也更易维护。

相关推荐
小贺要学前端1 小时前
【无标题】
前端·javascript·vue·技术趋势
前端摸鱼匠2 小时前
Vue 3 的全局组件注册:讲解如何全局注册组件
前端·javascript·vue.js·前端框架·node.js·ecmascript
lcc1873 小时前
Vue VueComponent
前端·vue.js
摇滚侠4 小时前
Vue 项目实战《尚医通》,预约挂号就诊人组件搭建上,笔记40
前端·javascript·vue.js·笔记
csdn_wuwt5 小时前
前后端中Dto是什么意思?
开发语言·网络·后端·安全·前端框架·开发
前端互助会7 小时前
Live2D形象展示与文本语音播报:打造生动交互体验的完整实现
前端·vue.js·microsoft·交互
努力的小郑9 小时前
今晚Cloudflare一哆嗦,我的加班计划全泡汤
前端·后端·程序员
q***649710 小时前
头歌答案--爬虫实战
java·前端·爬虫
凌波粒10 小时前
SpringMVC基础教程(4)--Ajax/拦截器/文件上传和下载
java·前端·spring·ajax