1 声明式 UI 与命令式 UI

一、核心思想对比
| 维度 | 命令式 UI | 声明式 UI |
|---|---|---|
| 关注重点 | 「怎么做」------ 需手动编写每一步操作的具体逻辑,明确指挥 UI "如何变化"。 | 「是什么」------ 只需声明 UI "最终应该是什么样",由框架 / 引擎自动处理 "如何变化" 的细节。 |
二、结合表单案例的实现方式对比
以文中 "用户提交答案的表单" 交互为例:
-
命令式 UI 的实现逻辑(需手动控制每一步):
- 监听 "表单输入" 事件 → 输入有内容时,手动设置 "提交按钮" 为可用状态;
- 监听 "提交按钮点击" 事件 → 点击后,手动设置 "表单" 和 "提交按钮" 为不可用,并手动启动 "加载动画";
- 网络请求成功 → 手动隐藏 "表单",手动显示 "提交成功" 信息;
- 网络请求失败 → 手动显示 "错误信息",手动将 "表单" 设为可用状态。
-
声明式 UI 的实现逻辑 (只需声明状态与 UI 的映射关系):定义几个核心状态(如
isFormEnabled、isLoading、isSuccess、isError),然后声明 "状态→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 变量和切换逻辑:
-
输入框文本变化 → 状态切换(Empty ↔ Typing)
- 定义
inputValue(记录输入框内容)和formState(记录当前表单状态,如'empty' | 'typing' | 'submitting' | 'error' | 'success')。 - 监听输入框的 "文本变化" 事件:
- 若
inputValue从空变为非空 →formState从'empty'切换到'typing',同时提交按钮变为可用。 - 若
inputValue从非空变为空 →formState从'typing'切换回'empty',提交按钮变为不可用。
- 若
- 定义
-
点击提交按钮 → 状态切换(Typing → Submitting)
- 定义点击事件处理函数,点击时将
formState设为'submitting',此时表单和提交按钮变为不可用,同时显示加载动画。
- 定义点击事件处理函数,点击时将
-
网络请求结果 → 状态切换(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?
以文中表单为例,若用多个独立状态变量(如 error、status、inputValue),可能出现逻辑矛盾的状态组合(比如 status=success 时 error 却非空)。而 Reducer 可以:
- 合并多状态为一个对象 :将
error、status、inputValue等状态封装到一个统一的状态对象中。 - 固化状态变化逻辑:通过 "动作(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 类的动态切换
一、挑战题目详情
需求:实现一个图片交互效果,具体如下:
- 初始状态:外部
<div>包含background--active类(视觉上显示紫色背景),内部<img>只有picture类(无高亮边框)。 - 点击图片时:
- 移除
<div>的background--active类(紫色背景消失)。 - 给
<img>添加picture--active类(图片边框高亮)。
- 移除
- 再次点击图片时:恢复初始状态(背景重新高亮,图片边框高亮消失)。
初始代码:
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.add或remove)。 - 但又想到 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:编写事件处理函数修改状态
点击图片时,需要切换状态(false 变 true,true 变 false),因此事件处理函数的逻辑是 "反转当前状态":
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。这种方式让代码更符合声明式理念,也更易维护。