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

React函数组件中如何获取映射组件的所有输入值并汇总

React函数组件实现动态费用项添加与实时总金额计算

核心实现思路

  • 父组件维护费用数据数组(而非JSX元素),每个费用项包含唯一ID、名称、金额、分类
  • 子组件负责单个费用项的渲染,通过回调函数将输入变化同步到父组件
  • 基于费用数组实时计算总金额,利用React状态更新自动触发重新计算

修改后的完整代码

父组件 ReceiptDetails

import { useState, useEffect } from 'react';
import ExpenseDetails from './ExpenseDetails';

const ReceiptDetails = () => {
  const [isOpen, setIsOpen] = useState(false);
  const [selectedOption, setSelectedOption] = useState(null);
  // 存储费用数据,而非JSX
  const [expenseList, setExpenseList] = useState([]);
  const [expenseTotal, setExpenseTotal] = useState(0);

  const options = ['Food', 'Entertainment', 'Houseware'];

  const onOptionClicked = (option) => {
    setSelectedOption(option);
    setIsOpen(false);
  };

  // 添加新费用项:生成唯一ID,初始化名称和金额
  const addExpense = (e) => {
    e.preventDefault();
    if (selectedOption) {
      const newExpense = {
        id: Date.now(), // 用时间戳做临时唯一ID
        category: selectedOption,
        name: '',
        amount: 0
      };
      setExpenseList(prev => [...prev, newExpense]);
    }
  };

  // 更新单个费用项的属性(名称/金额)
  const updateExpense = (id, field, value) => {
    setExpenseList(prev => 
      prev.map(item => 
        item.id === id ? {...item, [field]: field === 'amount' ? Number(value) : value} : item
      )
    );
  };

  // 删除费用项(可选,按需添加)
  const deleteExpense = (id) => {
    setExpenseList(prev => prev.filter(item => item.id !== id));
  };

  // 实时计算总金额
  useEffect(() => {
    const total = expenseList.reduce((sum, item) => sum + (item.amount || 0), 0);
    setExpenseTotal(total.toFixed(2)); // 保留两位小数
  }, [expenseList]);

  return (
    <div>
      <form onSubmit={addExpense}>
        <div>
          <div onClick={() => setIsOpen(!isOpen)} style={{padding: '8px', border: '1px solid #ccc', cursor: 'pointer'}} required>
            {selectedOption || 'Select Category'}
            <span className='arrow-head'>&#8964;</span>
          </div>
          {isOpen && (
            <div style={{border: '1px solid #ccc', marginTop: '4px'}}>
              {options.map((option, index) => (
                <div
                  onClick={() => onOptionClicked(option)}
                  key={index}
                  style={{padding: '8px', cursor: 'pointer'}}
                >
                  {option}
                </div>
              ))}
            </div>
          )}
        </div>
        <button type="submit" style={{marginTop: '10px'}}>Add expense</button>
      </form>
      
      {/* 渲染所有费用项 */}
      <div style={{marginTop: '20px'}}>
        {expenseList.map(expense => (
          <ExpenseDetails
            key={expense.id}
            expense={expense}
            updateExpense={updateExpense}
            deleteExpense={deleteExpense}
          />
        ))}
      </div>

      {/* 显示总金额 */}
      {expenseList.length > 0 && (
        <div style={{marginTop: '20px', fontSize: '18px', fontWeight: 'bold'}}>
          Total: €{expenseTotal}
        </div>
      )}
    </div>
  );
};

export default ReceiptDetails;

子组件 ExpenseDetails

const ExpenseDetails = ({ expense, updateExpense, deleteExpense }) => {
  return (
    <div style={{display: 'flex', gap: '10px', marginBottom: '10px', alignItems: 'center'}}>
      <input
        type="text"
        placeholder="Expense name"
        value={expense.name}
        onChange={(e) => updateExpense(expense.id, 'name', e.target.value)}
        style={{padding: '6px'}}
      />
      <input
        type="number"
        step="0.01"
        placeholder="€0.00"
        value={expense.amount}
        onChange={(e) => updateExpense(expense.id, 'amount', e.target.value)}
        style={{padding: '6px'}}
      />
      <span style={{color: expense.category, fontWeight: '500'}}>
        {expense.category}
      </span>
      <button
        onClick={() => deleteExpense(expense.id)}
        style={{padding: '6px 10px', backgroundColor: '#ff4444', color: 'white', border: 'none', cursor: 'pointer'}}
      >
        Delete
      </button>
    </div>
  );
};

export default ExpenseDetails;

关键修改说明

  1. 状态管理优化:父组件的expenseList存储的是数据对象而非JSX元素,这是React状态管理的最佳实践,便于后续的数据操作和计算。
  2. 数据同步机制:子组件通过updateExpense回调将输入框的变化传递给父组件,确保所有费用数据统一维护在父组件中。
  3. 实时计算总和:利用useEffect监听expenseList的变化,每次数组更新时自动计算总金额,确保UI实时同步。
  4. 唯一ID标识:用Date.now()生成临时唯一ID,避免渲染时的key冲突,生产环境可使用更可靠的ID生成库(如uuid)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 18:10:29