React受控自动调整高度的textarea出现闪烁与光标跳转问题求助
React受控自动调整高度的textarea出现闪烁与光标跳转问题求助
我之前也碰到过几乎一样的问题,结合你的代码来看,主要问题出在不必要的组件重渲染和手动操作DOM样式时的光标/高度处理逻辑上,下面是针对性的解决方案和优化后的代码:
问题根源分析
- 不必要的子组件重渲染:你的父组件
App里,每次tick更新时都会重新创建onSubmit的匿名函数,导致子组件ChatInput的props变化,触发无意义的重渲染——这就是编辑时(尤其是父组件tick触发时)光标跳转到末尾的核心原因之一。 - DOM样式操作导致的闪烁与光标问题:你在
handleChange里先把高度设为0px再重置为scrollHeight,这种硬重置会导致textarea高度瞬间塌陷产生闪烁;同时,手动修改高度+组件重渲染时,浏览器会默认把光标重置到内容末尾,打断编辑体验。
优化后的完整代码
父组件(App.js)
先通过useCallback缓存提交回调,避免子组件不必要重渲染:
import { useEffect, useState, useCallback } from "react"; import ChatInput from "./ChatInput"; export default function App() { const [tick, setTick] = useState(0); // 用useCallback缓存回调,只有依赖变化时才重新创建 const handleSubmit = useCallback((m) => { console.log("submit:", m); }, []); useEffect(() => { const id = setInterval(() => setTick(t => t + 1), 1000); return () => clearInterval(id); }, []); return ( <div style={{ padding: 24 }}> <p>Parent tick: {tick}</p> <ChatInput onSubmit={handleSubmit} /> </div> ); }
子组件(ChatInput.js)
重构高度调整逻辑,加入光标位置保存/恢复,消除闪烁:
import { useState, useRef, useEffect } from "react"; export default function ChatInput({ onSubmit }) { const [message, setMessage] = useState(""); const ref = useRef(null); const handleChange = (e) => { setMessage(e.target.value); }; const handleKeyDown = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); onSubmit(message); setMessage(""); } }; // 把高度调整逻辑移到useEffect,配合光标位置保存 useEffect(() => { const textarea = ref.current; if (!textarea) return; // 保存当前光标位置 const startPos = textarea.selectionStart; const endPos = textarea.selectionEnd; // 用auto替代0px,避免高度塌陷闪烁 textarea.style.height = "auto"; // 自动适配内容高度 textarea.style.height = `${textarea.scrollHeight}px`; // 恢复光标位置,解决编辑时跳转问题 textarea.selectionStart = startPos; textarea.selectionEnd = endPos; }, [message]); return ( <textarea ref={ref} value={message} onChange={handleChange} onKeyDown={handleKeyDown} rows={1} placeholder="Write your message..." style={{ width: "100%", resize: "none", overflow: "hidden", whiteSpace: "pre-wrap", wordBreak: "break-word", minHeight: "calc(1.5em + 16px)", // 初始高度:行高+上下padding maxHeight: "400px", // 限制最大高度,内容过多时显示滚动条 overflowY: "auto", padding: "8px 12px", lineHeight: "1.5", border: "1px solid #e5e7eb", borderRadius: "8px", fontSize: "16px" }} /> ); }
优化点说明
- 避免不必要重渲染:父组件用
useCallback包裹handleSubmit,确保只有当依赖项变化时才重新创建回调函数,子组件ChatInput不会因为父组件的tick更新而反复重渲染。 - 消除闪烁+光标跳转:
- 用
style.height = "auto"替代0px,避免textarea高度瞬间塌陷导致的闪烁; - 调整高度前后保存/恢复光标位置,彻底解决编辑时光标跳转到末尾的问题;
- 用
- 体验增强:增加
maxHeight和overflowY: auto,防止内容过多时textarea过高,同时保留滚动能力;优化了初始高度的计算,让默认状态更美观。
如果还是存在极轻微的闪烁,可以尝试把高度调整代码包裹在requestAnimationFrame里,让DOM操作在浏览器的下一帧执行,进一步提升平滑度:
useEffect(() => { const textarea = ref.current; if (!textarea) return; const startPos = textarea.selectionStart; const endPos = textarea.selectionEnd; requestAnimationFrame(() => { textarea.style.height = "auto"; textarea.style.height = `${textarea.scrollHeight}px`; textarea.selectionStart = startPos; textarea.selectionEnd = endPos; }); }, [message]);
内容来源于stack exchange
相关产品推荐
相关产品推荐

