用State响应输入
🌟 声明式UI vs 命令式UI
🔄 两种编程范式的对比
- 🤖 命令式编程:直接操作UI元素,告诉计算机如何更新界面js
// 命令式示例 button.disabled = true; loadingMessage.style.display = 'block'; errorMessage.style.display = 'none';
- 🧩 声明式编程:描述UI在不同状态下应该是什么样子,让React来决定如何更新jsx
// 声明式示例 return ( <button disabled={isLoading}> {isLoading ? '加载中...' : '提交'} </button> );
🚕 形象类比
- 🚗 命令式:像给司机指路"往前100米右转,然后第二个路口左转..."
- 🗺️ 声明式:告诉出租车司机"我要去购物中心",由他决定如何到达
🧠 声明式思考UI的方法
🔍 五步法构建响应式UI
- 🔎 定位不同视图状态:识别组件可能处于的所有可能状态
- 🔄 确定状态触发因素:什么用户操作或事件会导致状态变化?
- 💾 在内存中表示状态:使用useState定义必要的状态变量
- 🧹 删除不必要的状态:移除冗余和可计算的状态
- 🔌 连接事件处理函数:实现状态更新逻辑
🛠️ 步骤详解
1️⃣ 定位不同视图状态
- 📋 罗列所有可能状态:
- 🖋️ 输入中:显示空表单
- ⏳ 提交中:表单禁用,显示加载中
- ✅ 成功时:显示成功消息
- ❌ 错误时:显示错误消息和可编辑表单
jsx
// 声明式方式的状态处理
if (status === 'success') {
return <h1>提交成功!</h1>;
}
return (
<form>
{status === 'error' && <p className="error">{error.message}</p>}
<textarea disabled={status === 'submitting'} />
<button disabled={status === 'submitting' || answer.length === 0}>
提交
</button>
{status === 'submitting' && <p>提交中...</p>}
</form>
);
2️⃣ 确定状态触发因素
- 👆 用户交互:
- 输入文本 → 表单从空变为非空
- 点击提交 → 状态变为"提交中"
- 网络请求成功 → 状态变为"成功"
- 网络请求失败 → 状态变为"错误"
3️⃣ 使用useState表示状态
jsx
// 定义必要的状态变量
const [answer, setAnswer] = useState(''); // 用户输入
const [error, setError] = useState(null); // 错误信息
const [status, setStatus] = useState('typing'); // 当前状态:'typing', 'submitting', 'success'
4️⃣ 移除冗余状态
- ❓ 检查每个状态是否必要:
- ❌ 不要同时使用
isEmpty
和isTyping
(互斥) - ❌ 不要同时使用
isError
和error
(可通过error !== null
判断) - ✅ 用一个
status
变量代替多个布尔状态
- ❌ 不要同时使用
jsx
// ❌ 冗余状态
const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
// ✅ 优化后
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', 'success'
5️⃣ 实现事件处理函数
jsx
// 处理表单提交
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);
}
🔍 完整示例:表单提交流程
jsx
import { useState } from 'react';
export default function Form() {
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing');
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 (
<form onSubmit={handleSubmit}>
<textarea
value={answer}
onChange={handleTextareaChange}
disabled={status === 'submitting'}
/>
<br />
<button disabled={
answer.length === 0 ||
status === 'submitting'
}>
提交
</button>
{error !== null &&
<p className="Error">
{error.message}
</p>
}
</form>
);
}
🌟 使用Reducer优化复杂状态
🧩 Reducer的优势
- 📊 集中管理:将所有状态更新逻辑集中在一个地方
- 🚫 避免不一致:减少状态组合中的"不可能状态"
jsx
// 使用reducer管理表单状态
function formReducer(state, action) {
switch (action.type) {
case 'START_SUBMITTING':
return { ...state, status: 'submitting' };
case 'SUBMISSION_SUCCESS':
return { ...state, status: 'success' };
case 'SUBMISSION_ERROR':
return {
...state,
status: 'typing',
error: action.error
};
case 'UPDATE_ANSWER':
return {
...state,
answer: action.answer
};
default:
return state;
}
}
📝 总结
- 🎭 声明式vs命令式:React使用声明式编程,描述UI应该是什么样子,而非如何修改
- 🧩 状态驱动UI:将UI视为不同状态的集合,使用state控制当前显示哪个状态
- 🔄 单向数据流:状态变化→重新渲染→显示新UI,形成可预测的数据流
- 🧠 思考方式:先识别所有UI状态,然后编写代码使UI能够在这些状态间转换
- 🛠️ 最小状态原则:只保留必要的状态变量,删除可以从其他状态计算出的变量