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

React组件中双Span元素独立状态与样式保存问题求助

解决React Card组件中可编辑Span样式状态独立问题

问题场景

React Card组件内有两个可编辑的span元素,通过props接收CSS样式配置。当前存在核心问题:编辑其中一个span后切换到另一个时,当前的CSS样式会被错误应用到新激活的span,无法维持两个span各自独立的样式状态——每个span应在激活时加载自身初始样式,并独立应用props传递的样式。

核心问题分析

  1. useEffect依赖范围过大:原代码中useEffect依赖了props和spanText,导致只要props变化或任意span文本修改,就会触发样式更新,干扰两个span的独立状态。
  2. hover状态逻辑不合理:原hover事件同时控制两个span的显示状态,容易导致无关的显示冲突。
  3. 样式更新逻辑不精准:切换激活span时,没有针对当前激活的span做精准的样式更新,反而可能在props变化时意外覆盖未激活span的样式。

修复方案

  1. 拆分hover事件控制:为每个span单独绑定hover事件,各自控制自身的显示状态,避免互相干扰。
  2. 缩小useEffect依赖范围:仅在activeTextarea或对应样式props变化时,更新当前激活span的样式,避免无关状态触发更新。
  3. 精准更新样式状态:每个span的样式仅在自身被激活时,才用props更新其样式,确保两个span的状态完全独立。

修改后的完整代码

import React, { useState, useEffect } from 'react'
import Draggable from 'react-draggable';

const Card = (props) => {
    const [spanText, setSpanText] = useState("Type Here")
    const [spanText2, setSpanText2] = useState("Type Here")
    const [isHovered, setIsHovered] = useState(false);
    const [isHovered2, setIsHovered2] = useState(false);
    const [activeTextarea, setActiveTextarea] = useState(null);
    
    // 初始化每个span的独立样式状态
    const [span1Styles, setSpan1Styles] = useState({
        color: "white",
        fontSize: "19px",
        fontFamily: props.font
    });
    const [span2Styles, setSpan2Styles] = useState({
        color: "white",
        fontSize: "19px",
        fontFamily: props.font
    });

    const handleSpan1Click = () => {
        setActiveTextarea(1);
    };

    const handleSpan2Click = () => {
        setActiveTextarea(2);
    };

    // 为每个span单独绑定hover事件
    const handleSpan1MouseEnter = () => {
        setIsHovered(true);
    };
    const handleSpan1MouseLeave = () => {
        if (spanText === "Type Here") {
            setIsHovered(false);
        }
    };
    const handleSpan2MouseEnter = () => {
        setIsHovered2(true);
    };
    const handleSpan2MouseLeave = () => {
        if (spanText2 === "Type Here") {
            setIsHovered2(false);
        }
    };

    const handleChange = (e) => {
        setSpanText(e.target.value)
    }

    const handleChange2 = (e) => {
        setSpanText2(e.target.value)
    }

    const handleKeyDown = (event) => {
        if (event.key === "Enter") {
            event.preventDefault();
        }
    };

    useEffect(() => {
        // 仅在激活状态或对应样式props变化时,更新目标span的样式
        if (activeTextarea === 1) {
            setSpan1Styles(prev => ({
                ...prev,
                color: props.color,
                fontSize: `${props.fontSize}px`,
                fontFamily: props.font
            }));
        } else if (activeTextarea === 2) {
            setSpan2Styles(prev => ({
                ...prev,
                color: props.color,
                fontSize: `${props.fontSize}px`,
                fontFamily: props.font
            }));
        }
    }, [activeTextarea, props.color, props.fontSize, props.font]);

    return (
        <>
            <div className='flex myDIV justify-center mt-10 w-[100%] md:w-[90%] self-start relative'>
                <div
                    id="test"
                    onClick={props.handleEdit}
                    className="card parent parent-container test w-[100%]"
                    style={{
                        backgroundImage: "url(https://images.unsplash.com/photo-1684324500031-65fc8dcd4156?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1332&q=80)",
                        backgroundSize: "cover", backgroundPosition: "center center", backgroundRepeat: "no-repeat", aspectRatio: "16/9"
                    }}>
                    <Draggable
                        enableUserSelectHack={false}
                        axis="both"
                        handle=".handle"
                        position={null}
                        scale={1}
                        bounds=".card"
                    >
                        <div 
                            className="handle w-max"
                            onMouseEnter={handleSpan1MouseEnter}
                            onMouseLeave={handleSpan1MouseLeave}
                        >
                            {isHovered && (
                                <span
                                    onClick={handleSpan1Click}
                                    onInput={handleChange}
                                    onKeyDown={handleKeyDown}
                                    contentEditable
                                    suppressContentEditableWarning
                                    style={span1Styles}
                                    className={`bg-transparent text-[19px] text-white text-cairo outline-none hover:border whitespace-pre overflow-hidden resize-none`}
                                >
                                    {spanText}
                                </span>
                            )}
                        </div>
                    </Draggable>

                    <Draggable
                        enableUserSelectHack={false}
                        axis="both"
                        handle=".handle"
                        position={null}
                        scale={1}
                        bounds=".card"
                    >
                        <div 
                            className="handle w-max"
                            onMouseEnter={handleSpan2MouseEnter}
                            onMouseLeave={handleSpan2MouseLeave}
                        >
                            {isHovered2 && (
                                <span
                                    onClick={handleSpan2Click}
                                    onInput={handleChange2}
                                    onKeyDown={handleKeyDown}
                                    contentEditable
                                    suppressContentEditableWarning
                                    style={span2Styles}
                                    className={`bg-transparent text-[19px] text-white text-cairo outline-none hover:border whitespace-pre overflow-hidden resize-none`}
                                >
                                    {spanText2}
                                </span>
                            )}
                        </div>
                    </Draggable>
                </div>
            </div >
        </>
    )
}

export default Card

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 05:44:56