09 — State 与 useState
这一章讲 React 另一半核心:State — 组件内部会变化的数据。
学完这章你就理解了 React 自动更新 UI 的机制。
1. 问题引入:为什么需要 State?
1.1 普通变量不行吗?
1 2 3 4 5 6 7 8 9 10
| function Counter() { let count = 0;
return ( <div> <p>{count}</p> <button onClick={() => count++}>+1</button> </div> ); }
|
问题:点按钮,count 确实变了,但页面不更新。
原因:React 不知道你改了 count,所以不会重新渲染。
1.2 解决方案:useState
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { useState } from 'react';
function Counter() { const [count, setCount] = useState(0);
return ( <div> <p>{count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); }
|
现在点按钮:
- 调用
setCount(count + 1) - React 检测到 state 变化
- 自动重新调用 Counter() 函数
- 新的 JSX 包含新 count
- DOM 自动更新
2. useState 详解
2.1 语法
1 2 3
| const [value, setValue] = useState(initialValue);
|
- 初始值:只在第一次渲染时生效
- value:当前 state 值
- setValue:调用它会触发重新渲染
2.2 例子:各种类型的 State
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const [count, setCount] = useState(0);
const [name, setName] = useState('豆豆');
const [active, setActive] = useState(false);
const [list, setList] = useState([]);
const [user, setUser] = useState({ name: 'A', age: 30 });
const [chat, setChat] = useState({ id: '1', title: '新对话', messages: [], });
const [status, setStatus] = useState<'idle' | 'loading' | 'done'>('idle');
|
2.3 初始值可以是一个函数(懒初始化)
1 2 3 4 5 6 7 8
| const [count, setCount] = useState(0);
const [data, setData] = useState(() => { const initial = computeExpensiveValue(); return initial; });
|
项目里例子:
1 2 3 4 5 6 7 8 9 10
| const [collapsed, setCollapsed] = useState<boolean>(() => { if (typeof window === 'undefined') return false; try { return localStorage.getItem(STORAGE_KEY) === 'true'; } catch { return false; } });
|
3. 修改 State 的正确姿势
3.1 基本更新
1 2 3 4 5
| const [count, setCount] = useState(0);
setCount(5); setCount(count + 1); setCount(prev => prev + 1);
|
为什么用函数式更新?
1 2 3 4 5 6 7 8 9 10 11 12
| setCount(count + 1); setCount(count + 1); setCount(count + 1);
setCount(prev => prev + 1); setCount(prev => prev + 1); setCount(prev => prev + 1);
|
3.2 修改对象 State
重要原则:永远创建新对象,不要直接修改。
1 2 3 4 5 6 7 8 9
| const [user, setUser] = useState({ name: 'A', age: 30 });
user.age = 31; setUser(user);
setUser({ ...user, age: 31 }); setUser(prev => ({ ...prev, age: 31 }));
|
3.3 修改数组 State
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const [list, setList] = useState([1, 2, 3]);
setList([...list, 4]);
setList([0, ...list]);
setList(list.filter(item => item !== 2));
setList(list.map(item => item === 2 ? 20 : item));
list.push(4); setList(list);
|
3.4 项目里的真实例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function EmptyState({ onQuickPrompt }: Props) { return ( <div> {QUICK_ACTIONS.map((a) => ( <button key={a.text} onClick={() => onQuickPrompt(a.text)} > {a.text} </button> ))} </div> ); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| async sendMessage() { const userMsg: ChatMessage = { id: generateId('msg'), role: 'user', content: message, file: hasFile, fileName: fileToSend?.name ?? null, timestamp: Date.now(), }; set({ chatList: [...get().chatList], inputMessage: '', isSending: true, }); }
|
4. State 在哪里?
4.1 组件 State — useState
- 每个组件各自持有自己的 State
- 组件间不共享(除非用其他机制)
1 2 3 4 5 6 7 8 9 10 11
| function CounterA() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>A: {count}</button>; }
function CounterB() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>B: {count}</button>; }
|
4.2 全局 State — Zustand
项目里绝大多数 State 都在 Store 里(@/store/chatStore.ts)。
为什么?
- 多个组件需要共享数据(聊天列表、当前会话 ID)
- 跨组件通信太繁琐(层层传 Props)
Store 的本质:一个全局对象,组件按需订阅。
1 2 3 4
| const chatList = useChatStore((s) => s.chatList); const currentChatId = useChatStore((s) => s.currentChatId); const isSending = useChatStore((s) => s.isSending);
|
详细看 14-Zustand状态管理.md。
4.3 项目里 useState 用得不多
1 2
| grep -r "useState" src/ --include="*.tsx"
|
项目里只在以下地方用:
Sidebar.tsx — 折叠状态 collapsedSidebar.tsx — 媒体查询 useMediaQuery(自定义 Hook 内部用)InputArea.tsx 的 useTextareaAutosize — textarea ref
其他所有动态数据(聊天列表、消息、输入框文字、Agent 选择等)都在 Zustand Store。
5. State 变化触发的连锁反应
5.1 完整流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 用户操作(点按钮、输入文字) ↓ 调用 setState / store action ↓ React 收到通知 ↓ React 重新调用函数组件 ↓ 得到新的 JSX(虚拟 DOM) ↓ React 对比新旧 JSX(diff 算法) ↓ 更新真实 DOM(只更新变化的部分) ↓ 浏览器渲染
|
5.2 重新渲染的范围
1 2 3 4 5 6 7 8 9 10
| function App() { const [count, setCount] = useState(0); return ( <div> <Header /> <Counter count={count} /> <Footer /> </div> ); }
|
setCount(1) 触发 App 重新渲染。
React 会重新调用 App、Header、Counter、Footer。
但只更新实际变化的 DOM(这里 Counter 的数字变了,其他不变)。
性能优化:用 React.memo 或 useMemo 减少不必要的重新渲染(项目里没用,因为数据量小)。
6. 受控组件 — 项目的 input/textarea
6.1 什么是受控组件
组件的值由 React State 控制(而不是 DOM 自己管)。
1 2 3 4 5 6 7 8 9 10
| function MyInput() { const [value, setValue] = useState('');
return ( <input value={value} // ← value 由 state 控制 onChange={(e) => setValue(e.target.value)} // ← 变化时更新 state /> ); }
|
1 2 3 4 5 6 7 8 9 10 11 12
| function InputArea() { const inputMessage = useChatStore(s => s.inputMessage); const setInput = useChatStore(s => s.setInput);
return ( <textarea value={inputMessage} // ← 受控:值来自 Store onChange={(e) => setInput(e.target.value)} // ← 变化时更新 Store /> ); }
|
为什么用 Store 而不是 local state?
- 其他组件可能需要读取输入内容(比如快捷问题)
- 全局共享一份,避免数据不一致
6.3 非受控组件(项目里没有但要知道)
1 2 3 4 5 6
| function MyInput() { const ref = useRef(null); return <input ref={ref} defaultValue="初始值" />; }
|
项目里只用受控组件,逻辑更清晰。
7. 实战:项目里所有 useState 的位置和作用
1 2 3 4 5 6 7 8 9 10 11 12
| const ref = useRef<HTMLDivElement>(null);
const [collapsed, setCollapsed] = useState<boolean>(() => { return localStorage.getItem(STORAGE_KEY) === 'true'; });
const [matches, setMatches] = useState<boolean>(...);
|
仅此而已。其他所有「会变的数据」都在 Zustand。
8. 常见错误
错误 1:忘记 useState,直接修改
1 2 3 4 5 6 7
| let count = 0; count++;
const [count, setCount] = useState(0); setCount(count + 1);
|
错误 2:直接修改 State 对象
1 2 3 4 5 6 7 8
| const [user, setUser] = useState({ name: 'A' }); user.name = 'B'; setUser(user);
setUser({ ...user, name: 'B' }); setUser(u => ({ ...u, name: 'B' }));
|
错误 3:连续 setState 期望合并
1 2 3 4 5 6 7 8 9 10
| const [count, setCount] = useState(0); setCount(count + 1); setCount(count + 1); setCount(count + 1);
setCount(c => c + 1); setCount(c => c + 1); setCount(c => c + 1);
|
错误 4:把 useState 写到 if/for 里(违反 Hooks 规则)
1 2 3 4 5 6 7 8 9 10
| if (someCondition) { const [count, setCount] = useState(0); }
const [count, setCount] = useState(0); if (someCondition) { }
|
9. 一段话总结
State = 组件内部会变化、变化会触发重新渲染的数据。
useState(initial) 返回 [value, setValue],调用 setValue 触发重新渲染。
修改原则:对象/数组要创建新对象(不可变更新)。
项目里:极少用 useState(只有折叠状态等),其他数据都在 Zustand Store。
接下来
State 懂了。下一章讲 React 的另一半 — 怎么和「外部世界」交互(API、DOM、定时器):10-副作用useEffect与useRef.md。