React Portal新窗口中Quill编辑器工具栏未初始化问题求助
解决React Portal新窗口中Quill编辑器工具栏无响应问题
问题核心
通过React Portal在新窗口渲染Quill编辑器时,文本同步正常,但工具栏按钮点击无反应,原因是Quill默认将事件监听器绑定到主窗口的document对象,即使编辑器DOM在新窗口,点击事件仍会被发送到原窗口的document,导致新窗口的工具栏操作无法触发。
解决方案步骤
1. 修复NewWindowPortal的异步状态问题
原代码中使用setNewWindow异步更新状态,后续代码直接访问newWindow会拿到初始null值,导致DOM操作失效。改用useRef直接保存新窗口和容器实例:
export const NewWindowPortal = ({ onClose, children }: { onClose: () => void; children: (props) => ReactNode }) => { const ref = useRef(null); const containerRef = useRef<HTMLElement | null>(null); const newWindowRef = useRef<Window | null>(null); useEffect(() => { const newWindow = window.open("", "", "width=800,height=700,left=200,top=200"); if (!newWindow) return; newWindowRef.current = newWindow; // 复制主窗口head资源到新窗口 const parentHead = window.document.querySelector("head").childNodes; parentHead.forEach((item) => { if (item.nodeName === "TITLE") { newWindow.document.title = `${(item as HTMLElement).innerHTML} - begrunnelse`; } else { newWindow.document.head.appendChild(item.cloneNode(true)); } }); // 创建Portal容器并挂载到新窗口body const container = newWindow.document.createElement("div"); containerRef.current = container; newWindow.document.body.appendChild(container); // 监听新窗口关闭事件 const handleBeforeUnload = () => onClose(); newWindow.addEventListener("beforeunload", handleBeforeUnload); return () => { newWindow.removeEventListener("beforeunload", handleBeforeUnload); newWindow.close(); newWindowRef.current = null; containerRef.current = null; }; }, [onClose]); return containerRef.current ? createPortal( children({ ref, newDocument: newWindowRef.current?.document }), containerRef.current ) : null; };
2. 让Quill完全绑定到新窗口的document
修改CustomQuillEditor,确保所有DOM操作、事件监听都基于新窗口的document,并手动重绑定工具栏的点击事件:
export const CustomQuillEditor = ({ readOnly, defaultValue, onTextChange, ref, resize, newDocument }: EditorProps) => { const containerRef = useRef(null); const quill = useRef<Quill | null>(null); useEffect(() => { if (!containerRef.current || !newDocument) return; // 使用新窗口document创建编辑器容器 const editorContainer = newDocument.createElement("div"); const container = containerRef.current; container.appendChild(editorContainer); // 初始化Quill quill.current = new Quill(editorContainer, { theme: "snow", readOnly, modules: { history: {}, toolbar: readOnly ? false : { container: [["bold", "italic", "underline", { header: 3 }]], }, }, }); ref.current = quill.current; // 关键:将工具栏点击事件绑定到新窗口document const toolbar = quill.current.getModule("toolbar"); if (toolbar && !readOnly) { const toolbarElement = toolbar.container; // 移除默认绑定到主document的事件 toolbarElement.removeEventListener("click", toolbar.handler); // 重新绑定到新窗口document newDocument.addEventListener("click", toolbar.handler); } // 文本变更监听 quill.current.on(Quill.events.TEXT_CHANGE, () => { if (!quill.current) return; if (quill.current.getLength() <= 1) { onTextChange(""); } else { onTextChange(quill.current.getSemanticHTML().replaceAll("<p></p>", "<p><br/></p>")); } }); return () => { ref.current = null; if (quill.current && newDocument) { const toolbar = quill.current.getModule("toolbar"); if (toolbar) { newDocument.removeEventListener("click", toolbar.handler); } quill.current = null; } container.innerHTML = ""; }; }, [ref, newDocument, readOnly, onTextChange]); useEffect(() => { if (!quill.current) return; const currentHTML = quill.current.getSemanticHTML().replaceAll("<p></p>", "<p><br/></p>"); if (defaultValue !== currentHTML) { const updatedDelta = quill.current.clipboard.convert({ html: defaultValue }); quill.current.setContents(updatedDelta, "silent"); } }, [defaultValue]); return ( <div spellCheck={false} className={`ql-top-container ${readOnly ? "readonly" : ""} ${resize ? "resizable" : ""}`} ref={containerRef} ></div> ); };
3. 关键修改说明
- 异步状态修复:用
useRef直接保存实例,避免React state异步更新导致的DOM操作失效。 - 事件重绑定:Quill工具栏默认将点击事件绑定到主窗口document,需手动移除并重新绑定到新窗口document,确保事件能被正确捕获。
- 全链路使用新document:从容器创建到编辑器初始化,所有DOM操作都基于新窗口document,避免跨document的操作冲突。
验证效果
修改后,新窗口中的Quill工具栏点击事件会被正确绑定到新窗口document,按钮操作可正常触发,同时文本同步功能保持正常。
内容的提问来源于stack exchange,提问作者Leff
相关产品推荐
相关产品推荐

