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

如何避免自定义MUI TextField在重渲染时失去焦点

解决MUI多色文本输入框输入时失焦问题

问题重现

自定义了支持多色文本的MUI TextField组件,但每次输入内容变更时,输入框都会失去焦点,尝试设置autoFocus无效。

核心原因

  1. 组件实例重复创建:在InputProps的inputComponent中直接定义forwardRef组件,每次父组件重渲染时都会生成一个全新的组件实例,React会卸载旧组件并挂载新组件,导致焦点丢失。
  2. 直接修改props:在MultiColorTextField中直接修改传入的props对象,违反React不可变数据原则,可能触发额外重渲染。

解决方案

1. 提取自定义输入组件到外部

不要在父组件的渲染函数内定义inputComponent对应的组件,确保组件类型稳定:

// 定义在父组件外部,避免每次重渲染生成新组件类型
const CustomInputComponent = forwardRef((props: any, inputRef: any) => {
  const { subInputRef, setSubData, subData, focussedField } = props;
  return MultiColorTextField(
    props,
    inputRef,
    subInputRef,
    setSubData,
    subData,
    focussedField
  );
});

2. 修复MultiColorTextField的props修改问题

不要直接修改传入的props,而是合并新属性:

export default function MultiColorTextField(
  props: any,
  inputRef: any,
  subInputRef: any,
  setSubData: any,
  data: any,
  focussedField: any
) {
  // 合并属性,避免修改原props
  const mergedProps = {
    ...props,
    dangerouslySetInnerHTML: { __html: data },
  };

  return (
    <Typography
      ref={inputRef}
      {...mergedProps}
      component="div"
      variant="body1"
      contentEditable
      suppressContentEditableWarning
      onInput={(evt: any) => {
        // 记录当前光标位置,解决光标跳开头问题
        const selection = window.getSelection();
        const range = selection?.getRangeAt(0);
        const cursorOffset = range?.startOffset;

        setSubData(evt.target.innerText);

        // 状态更新后恢复光标位置
        setTimeout(() => {
          if (selection && range && inputRef.current) {
            selection.removeAllRanges();
            const newRange = document.createRange();
            const targetNode = inputRef.current.firstChild || inputRef.current;
            newRange.setStart(targetNode, cursorOffset || 0);
            newRange.collapse(true);
            selection.addRange(newRange);
            inputRef.current.focus();
          }
        }, 0);
      }}
      onFocus={() => {
        focussedField.current = props.id;
      }}
    ></Typography>
  );
}

3. 优化父组件的InputProps配置

使用useMemo稳定传递给自定义组件的props,减少不必要重渲染:

const YourParentComponent = () => {
  const [subData, setSubData] = useState("");
  const subInputRef = useRef(null);
  const focussedField = useRef("");

  // 用useMemo稳定props,仅在subData变化时更新
  const inputComponentProps = useMemo(() => ({
    subInputRef,
    setSubData,
    subData,
    focussedField,
  }), [subData]);

  return (
    <TextField
      id="id-alarm-msg-subject"
      label={t("AlarmMsgTmp.Create.Subject")}
      variant="standard"
      InputLabelProps={{ shrink: true }}
      inputProps={{
        contentEditable: true,
        suppressContentEditableWarning: true,
        dangerouslySetInnerHTML: {
          __html: alarmMsgTmp.formatMultiColorString(subData),
        } as any,
      }}
      InputProps={{
        inputComponent: (props) => <CustomInputComponent {...props} {...inputComponentProps} />,
        ref: subInputRef,
        sx: {
          "& .MuiInputBase-input": {
            overflow: "auto",
            height: "100px",
          },
        },
      }}
      fullWidth
      onKeyDown={(evt: any) => {
        if (evt.which === 13) {
          evt.preventDefault();
        }
      }}
    ></TextField>
  );
};

额外说明

  • 移除了autoFocus属性,因为它仅在组件首次挂载时生效,重渲染时无法恢复焦点,我们通过onInput里的手动焦点恢复逻辑替代。
  • 光标位置记录与恢复逻辑是可选的,但能解决状态更新后光标跳到输入框开头的问题,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 11:37:22