You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

关键逻辑说明

  1. 状态提升的原因:App需要用支出列表计算结余,Expenses需要操作支出列表,Expense需要删除单个项,把支出列表放在App里能保证所有组件拿到的是同一数据源,避免数据不一致。
  2. 单向数据流:子组件不能直接修改父组件的状态,只能通过父组件传递的回调方法(比如onAddExpense、onDeleteExpense)来触发状态变更,这是React的核心规则,能保证数据流向可追踪。
  3. 唯一ID的作用:每个支出项需要唯一的key属性(React列表渲染要求),同时删除时能精准定位要删除的项,这里用Date.now()做临时ID,生产环境可以用更可靠的生成方式(比如uuid库)。
  4. 受控组件:所有输入框(收入、新增支出的名称/金额)都是受控组件,值由状态控制,变更时同步更新状态,避免非受控组件的不可预测性。

内容的提问来源于stack exchange,提问作者scath

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 08:50:09