React动态添加删除支出并计算收支结余的实现方案及思路咨询
React 动态收支项实现:数据传递与结余计算方案
核心思路
React是单向数据流,跨组件的共享状态(比如支出列表、总支出)需要状态提升到所有需要用到它的组件的共同父组件(这里就是App)。子组件通过props接收状态和修改状态的方法,再把数据变更同步回父组件,最终由父组件计算结余。
组件拆分实现细节
1. App组件(顶层状态管理)
把收入、支出列表这两个核心状态存在App里,同时计算总支出和结余。通过props把支出列表、添加/删除支出的方法传给Expenses组件。
import { useState } from 'react'; import Expenses from './Expenses'; function App() { // 收入状态 const [income, setIncome] = useState(0); // 支出列表:每个项是{id: 唯一标识, name: 支出名称, amount: 金额} const [expenseList, setExpenseList] = useState([]); // 计算总支出 const totalExpenses = expenseList.reduce((sum, item) => sum + Number(item.amount), 0); // 计算结余 const balance = Number(income) - totalExpenses; // 新增支出的方法:传给Expenses组件调用 const addExpense = (newExpense) => { setExpenseList(prev => [...prev, { ...newExpense, id: Date.now() }]); // 用时间戳做临时唯一ID }; // 删除支出的方法:通过ID定位删除 const deleteExpense = (expenseId) => { setExpenseList(prev => prev.filter(item => item.id !== expenseId)); }; return ( <div className="app"> <div> <label>收入:</label> <input type="number" value={income} onChange={(e) => setIncome(e.target.value)} placeholder="输入收入金额" /> </div> {/* 把支出列表、新增/删除方法传给Expenses */} <Expenses expenses={expenseList} onAddExpense={addExpense} onDeleteExpense={deleteExpense} /> <div className="balance"> <h3>结余:{balance.toFixed(2)}</h3> </div> </div> ); } export default App;
2. Expenses组件(列表管理与操作)
不需要自己存支出列表状态,直接接收App传过来的expenses和操作方法。负责渲染支出列表、提供添加支出的入口,把单个支出项的渲染交给Expense组件,同时把删除方法传给每个Expense。
import { useState } from 'react'; import Expense from './Expense'; function Expenses({ expenses, onAddExpense, onDeleteExpense }) { // 新增支出的临时状态:只在本组件用,提交后传给App const [newExpenseName, setNewExpenseName] = useState(''); const [newExpenseAmount, setNewExpenseAmount] = useState(0); const handleAdd = () => { if (!newExpenseName || newExpenseAmount <= 0) return; // 简单校验 onAddExpense({ name: newExpenseName, amount: newExpenseAmount }); // 清空输入框 setNewExpenseName(''); setNewExpenseAmount(0); }; return ( <div className="expenses"> <h2>支出明细</h2> <div className="add-expense"> <input type="text" value={newExpenseName} onChange={(e) => setNewExpenseName(e.target.value)} placeholder="支出名称" /> <input type="number" value={newExpenseAmount} onChange={(e) => setNewExpenseAmount(e.target.value)} placeholder="金额" /> <button onClick={handleAdd}>添加支出</button> </div> {/* 渲染所有支出项,把删除方法和当前支出项传下去 */} {expenses.map(item => ( <Expense key={item.id} expense={item} onDelete={() => onDeleteExpense(item.id)} /> ))} <div className="total-expense"> <p>总支出:{expenses.reduce((sum, item) => sum + Number(item.amount), 0).toFixed(2)}</p> </div> </div> ); } export default Expenses;
3. Expense组件(单个支出项展示)
只负责展示单个支出的信息,接收父组件传过来的支出数据和删除方法,点击删除按钮时调用父组件的方法即可。不需要自己存状态,完全是受控组件。
function Expense({ expense, onDelete }) { return ( <div className="expense-item"> <span>{expense.name}</span> <span>¥{expense.amount}</span> <button onClick={onDelete}>删除</button> </div> ); } export default Expense;
关键逻辑说明
- 状态提升的原因:
App需要用支出列表计算结余,Expenses需要操作支出列表,Expense需要删除单个项,把支出列表放在App里能保证所有组件拿到的是同一数据源,避免数据不一致。 - 单向数据流:子组件不能直接修改父组件的状态,只能通过父组件传递的回调方法(比如
onAddExpense、onDeleteExpense)来触发状态变更,这是React的核心规则,能保证数据流向可追踪。 - 唯一ID的作用:每个支出项需要唯一的
key属性(React列表渲染要求),同时删除时能精准定位要删除的项,这里用Date.now()做临时ID,生产环境可以用更可靠的生成方式(比如uuid库)。 - 受控组件:所有输入框(收入、新增支出的名称/金额)都是受控组件,值由状态控制,变更时同步更新状态,避免非受控组件的不可预测性。
内容的提问来源于stack exchange,提问作者scath
相关产品推荐
相关产品推荐

