React中useState结合switch实现香料计数器的最佳实践及useEffect问题解决
问题描述
现有一个React组件,包含香料数组、计数器val和spiceValue,用于实现香料数量增减模块(每种香料有独立单价,点击±按钮进行增减操作)。请问如何遵循React最佳实践实现该逻辑?此外,代码中传递val作为参数调用useEffect时无法读取val值,该如何处理?
原代码:
export const SpiceBlock = ({ isCalculated }) => { const [spiceQuantity, setSpiceQuantity] = useState(0); var val = 0; var spiceValue = 0; useEffect((val) => { setSpiceQuantity(); }, []); const spicesCount = (event) => { const direction = event.target.dataset.direction; const dataSpice = event.target.dataset.spice; switch (dataSpice) { case "guarana":spiceValue = 21;break; case "zhen":spiceValue = 11;break; case "cinnamon":spiceValue = 33;break; case "tapioka":spiceValue = 41;break; default:return false; } if (direction === "plus") { val += spiceValue; } else if (val - spiceValue > 0) { val -= spiceValue; } }; const spicesArr = [ { name: "Guarana", data: "guarana" },{ name: "Zhenshen", data: "zhen" }, { name: "Cinnamon", data: "cinnamon" },{ name: "Tapioka", data: "tapioka" }, ]; return ( <div className={`spiceItems ${isCalculated ? "calculated" : ""}`}> {isCalculated && spicesArr ? spicesArr.map((spice, index) => ( <div className="counter" key={`spice${index}`}> <button className="counter__btn" data-direction="minus" data-spice={spice.data} onClick={spicesCount} > - </button> <label htmlFor="spice" type="text"> {spice.name} <input type="text" name="spice" value={val} disabled className="counter__value" /> {spiceQuantity} </label> <button className="counter__btn" data-direction="plus" data-spice={spice.data} onClick={spicesCount} > + </button> </div> )) : null} </div> ); };
一、遵循React最佳实践重构代码
原代码核心问题:用var声明的普通变量无法触发React重渲染、状态管理混乱、事件处理依赖DOM属性传递数据。以下是重构后的实现:
1. 核心优化点
- 用
useState管理所有需要触发重渲染的可变数据(每种香料的数量) - 抽离香料单价为常量,统一维护
- 事件处理直接传递参数,避免依赖DOM属性
- 使用函数式更新状态,确保获取最新值
- 列表渲染用稳定唯一的key,避免索引key的潜在问题
重构后代码
import { useState } from 'react'; // 抽离香料单价常量,便于维护 const SPICE_PRICES = { guarana: 21, zhen: 11, cinnamon: 33, tapioka: 41, }; export const SpiceBlock = ({ isCalculated }) => { // 用对象状态管理每种香料的数量,初始值均为0 const [spiceCounts, setSpiceCounts] = useState({ guarana: 0, zhen: 0, cinnamon: 0, tapioka: 0, }); // 计算总价:根据各香料数量和单价动态累加 const totalValue = Object.entries(spiceCounts).reduce((total, [spice, count]) => { return total + count * SPICE_PRICES[spice]; }, 0); // 处理香料数量增减:直接接收香料标识和操作方向 const handleSpiceCountChange = (spice, direction) => { setSpiceCounts(prevCounts => { const currentCount = prevCounts[spice]; if (direction === 'plus') { return { ...prevCounts, [spice]: currentCount + 1 }; } else { // 确保数量不会小于0 return { ...prevCounts, [spice]: Math.max(currentCount - 1, 0) }; } }); }; const spicesArr = [ { name: 'Guarana', data: 'guarana' }, { name: 'Zhenshen', data: 'zhen' }, { name: 'Cinnamon', data: 'cinnamon' }, { name: 'Tapioka', data: 'tapioka' }, ]; return ( <div className={`spiceItems ${isCalculated ? 'calculated' : ''}`}> {isCalculated && spicesArr.map((spice) => ( <div className="counter" key={`spice-${spice.data}`}> <button className="counter__btn" onClick={() => handleSpiceCountChange(spice.data, 'minus')} disabled={spiceCounts[spice.data] === 0} > - </button> <label htmlFor={`spice-${spice.data}`}> {spice.name} <input type="text" id={`spice-${spice.data}`} name="spice" value={spiceCounts[spice.data]} disabled className="counter__value" /> </label> <button className="counter__btn" onClick={() => handleSpiceCountChange(spice.data, 'plus')} > + </button> </div> ))} {/* 可选:显示总价 */} {isCalculated && ( <div className="total-value"> 总价:{totalValue} </div> )} </div> ); };
二、解决useEffect无法读取val的问题
原代码中useEffect的写法存在两个致命错误:
- useEffect回调不接收参数:你写的
useEffect((val) => { ... })里的val是回调函数的参数,不是组件内的val变量,因此必然是undefined。 - 依赖数组为空:即使
val是状态,空依赖数组会导致useEffect仅在组件挂载时执行一次,后续val变化不会触发回调。
正确写法(若需监听val变化)
首先要把val改为React状态,再在useEffect中监听:
const [val, setVal] = useState(0); useEffect(() => { // 此处可正常读取val的最新值 setSpiceQuantity(val); }, [val]); // 将val加入依赖数组,val变化时自动触发回调
在重构后的代码中,spiceQuantity已被替换为各香料的数量状态,因此该useEffect大概率不再需要。若需监听总价变化执行操作,可监听spiceCounts状态:
useEffect(() => { // 香料数量变化时执行逻辑 console.log('总价更新为:', totalValue); }, [spiceCounts]);
关键最佳实践总结
- 状态管理:所有需要触发重渲染的可变数据必须用
useState(或其他状态管理工具),禁止使用var/let声明的普通变量。 - 事件处理:避免通过DOM属性传递数据,直接在事件绑定中传入所需参数,代码更清晰且不易出错。
- 依赖数组:
useEffect的依赖数组必须包含所有回调中使用的外部变量,避免闭包陷阱。 - 常量抽离:固定配置(如香料单价)抽离为常量,便于维护和修改。
- 列表Key:列表渲染时使用稳定且唯一的标识作为key(如香料的
data字段),不要用索引。
内容的提问来源于stack exchange,提问作者kahjuksEi
相关产品推荐
相关产品推荐

