You need to enable JavaScript to run this app. React中如何通过复用单个组件处理多独立输入框并计算总价
优惠活动
大模型
产品
解决方案
定价
更多

React中如何通过复用单个组件处理多独立输入框并计算总价

问题根因

当前所有酒店条目共用了同一个全局状态:HotelList 中存储的是单个数字格式的 nights 状态,所有渲染出的 Hotel 组件都绑定这同一个数值、共用同一个修改方法,点击任意+按钮时修改的都是这个全局唯一的状态值,自然会出现所有输入框数值同步变化的问题。

实现方案

核心逻辑是将「每个酒店对应的入住晚数」与酒店唯一ID绑定,用键值对结构做独立状态管理,不再使用通用的全局数字状态。

具体实现步骤

  • 替换原有的单个nights数字状态,改为以酒店ID为键的对象结构,每个键值对存储对应酒店选择的独立晚数
  • 重构修改晚数的方法:给每个方法传入当前操作的酒店ID,只修改对应ID下的晚数数值,顺便补充减按钮、手动输入数值的逻辑,且限制晚数不能小于0
  • 关键注意点:每个传递给Hotel组件的修改方法,都要提前绑定当前条目的酒店ID,这样触发修改时才能精准定位要更新的状态,不会出现串值
  • 给每个Hotel组件传递对应ID下的独立晚数,以及绑定了对应ID的修改方法,让每个组件操作自己对应的状态
  • 总价计算时遍历所有酒店条目,逐行计算「每晚单价 * 对应晚数」后累加,直接渲染在页面上即可

修改后的完整代码

HotelList 组件

import React, { useState, useEffect } from 'react';
import './HotelList.scss';
import axios from 'axios';
import Hotel from '../Hotel/Hotel';

const HotelList = () => {
    const [hotel, setHotel] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState('');
    // 改为键值对结构的晚数状态:key是酒店ID,value是对应晚数
    const [nightsMap, setNightsMap] = useState({});

    useEffect(() => {
        const fetchData = async () => {
            try {
                const response = await axios.get('https://60007292cb21e10017af8f04.mockapi.io/api/v1/hotels')
                setHotel(response.data);
                setLoading(false);
            } catch (err) {
                setLoading(false);
                setError(err.message);
            }
        }
        fetchData();
    }, [])

    // 给指定ID的酒店晚数+1
    const incrementNights = (hotelId) => {
        setNightsMap(prev => ({
            ...prev,
            [hotelId]: (prev[hotelId] || 0) + 1
        }))
    }

    // 给指定ID的酒店晚数-1,且不小于0
    const decrementNights = (hotelId) => {
        setNightsMap(prev => ({
            ...prev,
            [hotelId]: Math.max(0, (prev[hotelId] || 0) - 1)
        }))
    }

    // 手动输入修改指定ID的酒店晚数
    const updateNights = (hotelId, value) => {
        setNightsMap(prev => ({
            ...prev,
            [hotelId]: Math.max(0, Number(value) || 0)
        }))
    }

    // 计算所有酒店的总价
    const totalPrice = hotel.reduce((sum, htl) => {
        const currentNights = nightsMap[htl.id] || 0;
        return sum + htl.price * currentNights;
    }, 0)

    return(
        <div>
            {!loading ? (
                <div>
                    {hotel.map(htl => (
                        <div key={htl.id}>
                            <hr/>
                            <Hotel 
                                title={htl.hotel} 
                                description={htl.description} 
                                price={htl.price} 
                                image={htl.photoURL}
                                // 传入当前酒店ID对应的独立晚数
                                nights={nightsMap[htl.id] || 0}
                                // 绑定当前酒店ID的修改方法,避免串值
                                onIncrement={() => incrementNights(htl.id)}
                                onDecrement={() => decrementNights(htl.id)}
                                onNightsChange={(value) => updateNights(htl.id, value)}
                            />
                        </div>
                    ))}
                    <p>Total price: ${totalPrice}</p>
                </div>
            ) : (
                <div className="ring"><div className="lds-dual-ring"></div></div>
            )}
        </div>
    );
};

export default HotelList;

Hotel 组件

import React from 'react';
import './Hotel.scss';

const Hotel = ({ title, description, price, image, nights, onIncrement, onDecrement, onNightsChange }) => {
    return(
        <div className="container">
            <div className="img">
                <img src={image} alt="hotel" />
            </div>
            <div className="info">
                <h1>{title}</h1>
                <p>{description}</p>
            </div>
            <div className="vl"></div>
            <div className="price">
                <button onClick={onDecrement}>-</button>
                <input 
                    type="number" 
                    min="0"
                    value={nights}
                    onChange={(e) => onNightsChange(e.target.value)}
                />
                <button onClick={onIncrement}>+</button>
            </div>
            <div className="inner-container">
                <i className="fas fa-trash"></i>
                <p>${price}</p>
            </div>
        </div>
    )
};

export default Hotel;
效果说明

修改后每个酒店的加减按钮触发时,只会修改nightsMap中对应酒店ID的数值,不会影响其他酒店的晚数选择;总价会随每个酒店的晚数变化实时计算更新,不会出现所有输入框联动的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.08 19:24:07