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

React中优化同级textarea组件高度同步的性能问题

问题描述

需求目标

  • 让同一行内的所有<textarea/>组件保持相同的height样式
  • 当任意一个<textarea/>的高度变化时,该行所有同组件同步更新高度

当前实现方案

父组件(状态提升)

通过将高度状态提升至行组件,实现该行所有子组件共享高度值:

const TableRow = (props: TableRowProps) => {
  const [textAreaHeight, setTextAreaHeight] = useState<string>("100%");

  console.log("parent render");

  return (
    <tr>
      {props.currentRow.cells.map((rowValue) => (
        <DynamicCellTextArea
          key={rowValue.id}
          currentRow={rowValue}
          height={textAreaHeight}
          setHeight={setTextAreaHeight}
        />
      ))}
    </tr>
  );
};

子组件(MutationObserver监听)

使用MutationObserver监听textarea的style属性变化,更新共享状态以同步所有同组件高度:

const DynamicCellTextArea = (props: DynamicCellTextAreaProps) => {
  const textareaRef = useRef<HTMLTextAreaElement>(null);

  useEffect(() => {
    if (textareaRef.current === null) {
      return;
    }

    textareaRef.current.style.minWidth = "100%";
    textareaRef.current.style.minHeight = "100%";
    textareaRef.current.style.height = props.height;

    const mutationObserver = new MutationObserver(() => {
      props.setHeight(textareaRef.current?.style.height as string);
    });

    mutationObserver.observe(textareaRef.current, {
      attributes: true,
      attributeFilter: ["style"],
    });
  }, [props]);

  return (
    <td className="h-px border border-red-400 overflow">
      <div className="flex w-full h-full">
        <textarea
          ref={textareaRef}
          rows={1}
          className="w-full px-3 py-2 bg-red-500 resize"
        />
      </div>
    </td>
  );
}

现有问题

表格包含大量行列时,渲染卡顿明显。曾尝试用onResize/onResizeCapture事件捕获resize动作来更新状态,但未成功,寻求优化方案。


优化方案

1. 减少不必要的重渲染

父组件优化

用useCallback稳定状态更新函数的引用,避免每次渲染生成新函数导致子组件无意义重渲染:

const TableRow = (props: TableRowProps) => {
  const [textAreaHeight, setTextAreaHeight] = useState<string>("100%");

  const handleHeightChange = useCallback((newHeight: string) => {
    setTextAreaHeight(newHeight);
  }, []);

  return (
    <tr>
      {props.currentRow.cells.map((rowValue) => (
        <DynamicCellTextArea
          key={rowValue.id}
          currentRow={rowValue}
          height={textAreaHeight}
          setHeight={handleHeightChange}
        />
      ))}
    </tr>
  );
};

子组件优化

给子组件添加React.memo,仅当props真正变化时才重新渲染:

const DynamicCellTextArea = React.memo((props: DynamicCellTextAreaProps) => {
  // 原有子组件逻辑
});

2. 替换MutationObserver为ResizeObserver

MutationObserver会监听所有style属性变化,容易触发冗余回调。改用ResizeObserver可精准监听元素尺寸变化:

useEffect(() => {
  if (!textareaRef.current) return;

  const textarea = textareaRef.current;
  textarea.style.minWidth = "100%";
  textarea.style.minHeight = "100%";
  textarea.style.height = props.height;

  const resizeObserver = new ResizeObserver((entries) => {
    const { height } = entries[0].contentRect;
    // 仅当高度变化超过1px时更新,避免频繁触发
    if (Math.abs(parseFloat(textarea.style.height) - height) > 1) {
      props.setHeight(`${height}px`);
    }
  });

  resizeObserver.observe(textarea);

  return () => {
    resizeObserver.unobserve(textarea);
  };
}, [props.height, props.setHeight]);

3. 防抖处理高度更新

若ResizeObserver仍触发过于频繁,可添加防抖函数减少状态更新次数:

// 定义防抖工具函数
const debounce = (fn: Function, delay: number) => {
  let timeoutId: NodeJS.Timeout;
  return (...args: any[]) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), delay);
  };
};

// 在子组件effect中使用
useEffect(() => {
  if (!textareaRef.current) return;

  const textarea = textareaRef.current;
  textarea.style.minWidth = "100%";
  textarea.style.minHeight = "100%";
  textarea.style.height = props.height;

  const debouncedSetHeight = debounce((height: string) => {
    props.setHeight(height);
  }, 100);

  const resizeObserver = new ResizeObserver((entries) => {
    const { height } = entries[0].contentRect;
    debouncedSetHeight(`${height}px`);
  });

  resizeObserver.observe(textarea);

  return () => {
    resizeObserver.unobserve(textarea);
    clearTimeout(debouncedSetHeight as any);
  };
}, [props.height, props.setHeight]);

4. 优化父组件渲染

若TableRow因其他props频繁触发渲染,可添加React.memo并自定义比较逻辑:

const TableRow = React.memo((props: TableRowProps) => {
  // 原有父组件逻辑
}, (prevProps, nextProps) => {
  // 仅当行ID变化时才重新渲染
  return prevProps.currentRow.id === nextProps.currentRow.id;
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 13:00:39