03-状态管理与路由——01. useState + Props - 状态提升 01. useState Props - 状态提升一、5W1H 概述维度内容What将多个组件共享的状态提升到最近的共同父组件中Why实现兄弟组件间的数据共享和同步When多个组件需要共享同一状态、兄弟组件通信Where共同父组件中定义状态通过 props 传递给子组件Who需要实现简单状态共享的开发者How父组件使用 useState通过 props 传递状态和更新函数二、What - 什么是状态提升状态提升Lifting State Up是 React 中的一种模式当多个组件需要共享同一状态时将该状态提升到它们的共同父组件中然后通过 props 传递给子组件。// ❌ 状态在各自组件中无法共享 function ComponentA() { const [data, setData] useState(); } function ComponentB() { const [data, setData] useState(); } // ✅ 状态提升到父组件 function Parent() { const [data, setData] useState(); return ( ComponentA data{data} setData{setData} / ComponentB data{data} setData{setData} / / ); }三、Why - 为什么需要状态提升3.1 解决兄弟组件通信问题在 React 中数据是单向流动的。兄弟组件之间无法直接共享状态需要通过父组件中转。3.2 保持数据一致性将状态提升后所有子组件都使用同一份数据确保数据同步。3.3 简化状态管理对于简单的父子/兄弟通信状态提升是最直接、最简单的方案。四、When - 何时使用状态提升场景是否使用说明兄弟组件需要共享状态✅ 推荐最直接的解决方案父子组件需要共享状态✅ 推荐原生 props 传递跨多层组件共享状态❌ 不推荐会导致 props drilling全局状态❌ 不推荐使用 Context 或状态管理库五、Where - 在哪里使用共同父组件中定义状态子组件中通过 props 接收状态和更新函数// 父组件 function Parent() { const [sharedState, setSharedState] useState(initialValue); return Child state{sharedState} setState{setSharedState} /; } // 子组件 function Child({ state, setState }) { return button onClick{() setState(newValue)}{state}/button; }六、Who - 谁需要使用所有需要实现组件间状态共享的 React 开发者。七、How - 如何使用状态提升7.1 基础示例兄弟组件通信// 父组件 function Parent() { const [sharedData, setSharedData] useState(); return ( div ChildA sharedData{sharedData} setSharedData{setSharedData} / ChildB sharedData{sharedData} / /div ); } // 子组件 A可以修改状态 function ChildA({ sharedData, setSharedData }) { return ( div input value{sharedData} onChange{(e) setSharedData(e.target.value)} placeholder输入内容... / /div ); } // 子组件 B只读取状态 function ChildB({ sharedData }) { return div接收到的数据: {sharedData}/div; }7.2 计数器示例function CounterParent() { const [count, setCount] useState(0); return ( div CounterDisplay count{count} / CounterControls setCount{setCount} / /div ); } function CounterDisplay({ count }) { return h1当前计数: {count}/h1; } function CounterControls({ setCount }) { return ( div button onClick{() setCount(prev prev 1)}1/button button onClick{() setCount(prev prev - 1)}-1/button button onClick{() setCount(0)}重置/button /div ); }7.3 表单示例function FormParent() { const [formData, setFormData] useState({ username: , email: , age: }); const handleChange (field, value) { setFormData(prev ({ ...prev, [field]: value })); }; return ( div FormInput label用户名 value{formData.username} onChange{(value) handleChange(username, value)} / FormInput label邮箱 value{formData.email} onChange{(value) handleChange(email, value)} / FormInput label年龄 value{formData.age} onChange{(value) handleChange(age, value)} / FormPreview data{formData} / /div ); } function FormInput({ label, value, onChange }) { return ( div label{label}:/label input value{value} onChange{(e) onChange(e.target.value)} / /div ); } function FormPreview({ data }) { return ( div h3预览/h3 p用户名: {data.username}/p p邮箱: {data.email}/p p年龄: {data.age}/p /div ); }7.4 Todo 列表示例function TodoApp() { const [todos, setTodos] useState([ { id: 1, text: 学习 React, completed: false }, { id: 2, text: 学习状态提升, completed: false } ]); const addTodo (text) { setTodos(prev [...prev, { id: Date.now(), text, completed: false }]); }; const toggleTodo (id) { setTodos(prev prev.map(todo todo.id id ? { ...todo, completed: !todo.completed } : todo ) ); }; const deleteTodo (id) { setTodos(prev prev.filter(todo todo.id ! id)); }; return ( div TodoInput onAdd{addTodo} / TodoList todos{todos} onToggle{toggleTodo} onDelete{deleteTodo} / TodoStats todos{todos} / /div ); } function TodoInput({ onAdd }) { const [text, setText] useState(); const handleSubmit (e) { e.preventDefault(); if (text.trim()) { onAdd(text); setText(); } }; return ( form onSubmit{handleSubmit} input value{text} onChange{(e) setText(e.target.value)} / button typesubmit添加/button /form ); } function TodoList({ todos, onToggle, onDelete }) { return ( ul {todos.map(todo ( li key{todo.id} input typecheckbox checked{todo.completed} onChange{() onToggle(todo.id)} / span style{{ textDecoration: todo.completed ? line-through : none }} {todo.text} /span button onClick{() onDelete(todo.id)}删除/button /li ))} /ul ); } function TodoStats({ todos }) { const total todos.length; const completed todos.filter(t t.completed).length; const active total - completed; return ( div p总计: {total} | 已完成: {completed} | 未完成: {active}/p /div ); }7.5 购物车示例function CartApp() { const [cart, setCart] useState([]); const addToCart (product) { setCart(prev { const existing prev.find(item item.id product.id); if (existing) { return prev.map(item item.id product.id ? { ...item, quantity: item.quantity 1 } : item ); } return [...prev, { ...product, quantity: 1 }]; }); }; const updateQuantity (id, quantity) { setCart(prev prev.map(item item.id id ? { ...item, quantity: Math.max(0, quantity) } : item ).filter(item item.quantity 0) ); }; const totalPrice cart.reduce((sum, item) sum item.price * item.quantity, 0); return ( div ProductList onAddToCart{addToCart} / Cart cart{cart} onUpdateQuantity{updateQuantity} / CartTotal totalPrice{totalPrice} / /div ); } function ProductList({ onAddToCart }) { const products [ { id: 1, name: 商品 A, price: 100 }, { id: 2, name: 商品 B, price: 200 }, { id: 3, name: 商品 C, price: 150 } ]; return ( div {products.map(product ( div key{product.id} span{product.name} - ¥{product.price}/span button onClick{() onAddToCart(product)}加入购物车/button /div ))} /div ); } function Cart({ cart, onUpdateQuantity }) { if (cart.length 0) { return p购物车是空的/p; } return ( ul {cart.map(item ( li key{item.id} {item.name} - ¥{item.price} x {item.quantity} button onClick{() onUpdateQuantity(item.id, item.quantity 1)}/button button onClick{() onUpdateQuantity(item.id, item.quantity - 1)}-/button /li ))} /ul ); } function CartTotal({ totalPrice }) { return h3总计: ¥{totalPrice}/h3; }八、状态提升 vs Props Drilling// ❌ 不好的做法过度提升导致 props drilling function App() { const [user, setUser] useState(null); return ( Header user{user} / Main user{user} setUser{setUser} / Footer user{user} / ); } // ✅ 好的做法只提升必要的状态 function App() { return ( Header / UserSection / {/* 用户状态只在这里使用 */} Footer / ); } function UserSection() { const [user, setUser] useState(null); return Profile user{user} setUser{setUser} /; }九、常见陷阱9.1 过度提升// ❌ 将不需要共享的状态也提升了 function Parent() { const [count, setCount] useState(0); const [text, setText] useState(); // 只在 ChildA 中使用 return ( ChildA count{count} setCount{setCount} text{text} setText{setText} / ChildB count{count} / / ); } // ✅ 只提升共享的状态 function Parent() { const [count, setCount] useState(0); return ( ChildA count{count} setCount{setCount} / ChildB count{count} / / ); }9.2 忘记使用函数式更新// ❌ 可能导致过期状态问题 function Parent() { const [count, setCount] useState(0); const incrementTwice () { setCount(count 1); setCount(count 1); // 只增加 1 }; return Child incrementTwice{incrementTwice} /; } // ✅ 使用函数式更新 function Parent() { const [count, setCount] useState(0); const incrementTwice () { setCount(prev prev 1); setCount(prev prev 1); // 增加 2 }; return Child incrementTwice{incrementTwice} /; }十、练习题基础题实现一个温度转换器摄氏度和华氏度互相转换实现一个简单的计算器两个数字输入显示计算结果进阶题实现一个可编辑的表格支持添加、删除、编辑行十一、小结要点说明适用场景兄弟组件通信、简单状态共享实现方式父组件 useState props 传递优点简单直接无需额外依赖缺点深层传递会导致 props drilling