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

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的写法存在两个致命错误:

  1. useEffect回调不接收参数:你写的useEffect((val) => { ... })里的val是回调函数的参数,不是组件内的val变量,因此必然是undefined。
  2. 依赖数组为空:即使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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 14:55:19