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'>⌄</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;
关键修改说明
- 状态管理优化:父组件的
expenseList存储的是数据对象而非JSX元素,这是React状态管理的最佳实践,便于后续的数据操作和计算。 - 数据同步机制:子组件通过
updateExpense回调将输入框的变化传递给父组件,确保所有费用数据统一维护在父组件中。 - 实时计算总和:利用
useEffect监听expenseList的变化,每次数组更新时自动计算总金额,确保UI实时同步。 - 唯一ID标识:用
Date.now()生成临时唯一ID,避免渲染时的key冲突,生产环境可使用更可靠的ID生成库(如uuid)。
内容的提问来源于stack exchange,提问作者nemar
相关产品推荐
相关产品推荐

