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

React受控自动调整高度的textarea出现闪烁与光标跳转问题求助

React受控自动调整高度的textarea出现闪烁与光标跳转问题求助

我之前也碰到过几乎一样的问题,结合你的代码来看,主要问题出在不必要的组件重渲染和手动操作DOM样式时的光标/高度处理逻辑上,下面是针对性的解决方案和优化后的代码:

问题根源分析

  1. 不必要的子组件重渲染:你的父组件App里,每次tick更新时都会重新创建onSubmit的匿名函数,导致子组件ChatInput的props变化,触发无意义的重渲染——这就是编辑时(尤其是父组件tick触发时)光标跳转到末尾的核心原因之一。
  2. 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"
      }}
    />
  );
}

优化点说明

  1. 避免不必要重渲染:父组件用useCallback包裹handleSubmit,确保只有当依赖项变化时才重新创建回调函数,子组件ChatInput不会因为父组件的tick更新而反复重渲染。
  2. 消除闪烁+光标跳转:
    • 用style.height = "auto"替代0px,避免textarea高度瞬间塌陷导致的闪烁;
    • 调整高度前后保存/恢复光标位置,彻底解决编辑时光标跳转到末尾的问题;
  3. 体验增强:增加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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 08:14:29