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

React Quill编辑器onChange触发时失焦问题求助

React Quill onChange触发后失焦问题解决方案

问题根源分析

你的代码存在两个核心问题导致失焦:

  • 内部组件重复创建:Editor组件定义在EditorFirstComp内部,每次父组件因fieldList或content变化重渲染时,这个内部组件都会被重新定义,导致ReactQuill实例被销毁重建,直接丢失焦点。
  • 错误使用defaultValue:ReactQuill作为受控组件时,应该用value属性而非defaultValue,defaultValue仅用于初始化,后续content变化不会同步到编辑器,反而会引发不必要的重渲染逻辑。

另外你尝试的useRef直接focus会定位到开头,是因为没有保存之前的光标位置,恢复时直接重置到起始点。

修复步骤及代码示例

1. 把内部Editor组件移到外部

将Editor组件从EditorFirstComp内部提取出来,避免每次父组件重渲染都重新创建组件。

2. 改用value属性绑定content

将ReactQuill的defaultValue替换为value,确保编辑器内容和props的content同步。

3. 保存光标位置并恢复焦点

使用useRef保存Quill实例和光标位置,在content更新后恢复到之前的位置。

完整修复后的代码:

import React, { useRef, useEffect, useState } from "react";
import { connect } from "react-redux";
import ReactQuill, { Quill } from "react-quill";
import "quill-mention";
import "react-quill/dist/quill.snow.css";

// 把Editor组件移到外部,避免父组件重渲染时重复创建
const Editor = ({ setContent, content, modules }) => {
  const quillRef = useRef(null);
  const [lastSelection, setLastSelection] = useState(null);

  // 保存光标位置
  useEffect(() => {
    const quill = quillRef.current?.getEditor();
    if (quill) {
      quill.on("selection-change", (range) => {
        if (range) {
          setLastSelection(range);
        }
      });
    }
    return () => {
      const quill = quillRef.current?.getEditor();
      if (quill) {
        quill.off("selection-change");
      }
    };
  }, []);

  // 内容更新后恢复光标位置
  useEffect(() => {
    const quill = quillRef.current?.getEditor();
    if (quill && lastSelection) {
      // 延迟执行确保内容已更新
      setTimeout(() => {
        quill.setSelection(lastSelection);
      }, 0);
    }
  }, [content]);

  const handleChange = (value) => {
    setContent(value);
  };

  return (
    <form>
      <ReactQuill
        ref={quillRef}
        theme="snow"
        value={content} // 改用value绑定受控内容
        onChange={handleChange}
        modules={modules}
      />
    </form>
  );
};

const EditorFirstComp = ({
  setContent,
  content,
  fieldList,
  setDataFromEditor,
}) => {
  const atValues = fieldList.map(
    (field) => ({ id: field.key, value: field.label })
  );

  const hashValues = [
    { id: 3, value: "Fredrik Sundqvist 2" },
    { id: 4, value: "Patrik Sjölin 2" },
  ];

  const mentionModuleConfig = {
    allowedChars: /^[A-Za-z\sÅÄÖåäö]*$/,
    mentionDenotationChars: ["@", "#"],
    source: function (searchTerm, renderList, mentionChar) {
      let values = mentionChar === "@" ? atValues : hashValues;

      if (searchTerm.length === 0) {
        renderList(values, searchTerm);
      } else {
        const matches = values.filter(item => 
          item.value.toLowerCase().includes(searchTerm.toLowerCase())
        );
        renderList(matches, searchTerm);
      }
    },
  };

  const modules = {
    mention: mentionModuleConfig,
  };

  return (
    <Editor
      setContent={setContent}
      content={content}
      modules={modules}
    />
  );
};

const mapStateToProps = (state) => ({
  fieldList: state.field,
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(EditorFirstComp);

额外优化点

  • 简化了atValues的生成逻辑,去掉了多余的数组包裹
  • 用filter替代了原始的for循环,代码更简洁易读
  • 给Quill的事件监听添加了清理函数,避免内存泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 23:06:26