React组件中双Span元素独立状态与样式保存问题求助
解决React Card组件中可编辑Span样式状态独立问题
问题场景
React Card组件内有两个可编辑的span元素,通过props接收CSS样式配置。当前存在核心问题:编辑其中一个span后切换到另一个时,当前的CSS样式会被错误应用到新激活的span,无法维持两个span各自独立的样式状态——每个span应在激活时加载自身初始样式,并独立应用props传递的样式。
核心问题分析
- useEffect依赖范围过大:原代码中
useEffect依赖了props和spanText,导致只要props变化或任意span文本修改,就会触发样式更新,干扰两个span的独立状态。 - hover状态逻辑不合理:原hover事件同时控制两个span的显示状态,容易导致无关的显示冲突。
- 样式更新逻辑不精准:切换激活span时,没有针对当前激活的span做精准的样式更新,反而可能在props变化时意外覆盖未激活span的样式。
修复方案
- 拆分hover事件控制:为每个span单独绑定hover事件,各自控制自身的显示状态,避免互相干扰。
- 缩小useEffect依赖范围:仅在
activeTextarea或对应样式props变化时,更新当前激活span的样式,避免无关状态触发更新。 - 精准更新样式状态:每个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
相关产品推荐
相关产品推荐

