createPortal新窗口无法渲染CustomQuillEditor的修复方案
修复Quill编辑器在新窗口Portal中空白的问题
问题背景
实现了NewWindowPortal组件用于在新窗口渲染内容,普通组件能正常显示,但传入CustomQuillEditor时,新窗口打开后仅显示空白文档,无内容渲染。
原NewWindowPortal组件代码
export const NewWindowPortal = ({ children }: { children: (props) => ReactNode }) => { const ref = useRef(null); const containerEl = useMemo(() => document.createElement("div"), []); useEffect(() => { const externalWindow = window.open("", "", "width=600,height=400,left=200,top=200"); externalWindow?.document?.body?.appendChild(containerEl); }, []); return ( <> <BodyShort>This is a new window</BodyShort> {createPortal(children({ ref }), containerEl)} </> ); };
组件调用代码
{openInNewWindow && ( <NewWindowPortal> {(props) => ( <CustomQuillEditor ref={props.ref} resize={resize} readOnly={lesemodus || readOnly} defaultValue={reformatText(value)} onTextChange={onTextChange} /> )} </NewWindowPortal> )}
原CustomQuillEditor组件代码
export const CustomQuillEditor = ({ readOnly, defaultValue, onTextChange, ref, resize }: EditorProps) => { const containerRef = useRef(null); useEffect(() => { const container = containerRef.current; const editorContainer = container.appendChild(container.ownerDocument.createElement("div")); const quill = new Quill(editorContainer, { theme: "snow", readOnly, modules: { history: {}, toolbar: readOnly ? false : { container: [ ["bold", "italic", "underline", { header: 3 }], // [{ 'color': "red" }, { 'background': "yellow" }] ], }, clipboard: { allowed: { tags: ["strong", "h3", "h4", "em", "p", "br", "span", "u"], // attributes: ['href', 'rel', 'target', 'class', "style"] attributes: [], }, customButtons: [], keepSelection: true, substituteBlockElements: true, magicPasteLinks: false, removeConsecutiveSubstitutionTags: false, }, }, }); ref.current = quill; if (defaultValue) { const delta = quill.clipboard.convert({ html: defaultValue }); quill.setContents(delta, "silent"); quill.history.clear(); } quill.on(Quill.events.TEXT_CHANGE, () => { if (quill.getLength() <= 1) { onTextChange(""); } else { onTextChange(quill.getSemanticHTML().replaceAll("<p></p>", "<p><br/></p>")); } }); return () => { ref.current = null; container.innerHTML = ""; }; }, [ref]); return ( <div spellCheck={false} className={`ql-top-container ${readOnly ? "readonly" : ""} ${resize ? "resizable" : ""}`} ref={containerRef} ></div> ); };
问题原因
- 文档上下文不匹配:组件通过Portal挂载到新窗口后,容器的文档上下文切换为新窗口的document,但原
useEffect依赖仅为ref,不会重新初始化编辑器,导致Quill实例绑定到原页面document,无法在新窗口渲染。 - 依赖项缺失:
useEffect依赖数组只包含ref,readOnly、defaultValue等关键props变化时不会触发编辑器重新初始化,更新逻辑异常。 - 事件监听未清理:未移除Quill的
TEXT_CHANGE事件监听,可能引发内存泄漏。
修复方案
修复后的CustomQuillEditor组件
export const CustomQuillEditor = ({ readOnly, defaultValue, onTextChange, ref, resize }: EditorProps) => { const containerRef = useRef(null); useEffect(() => { const container = containerRef.current; if (!container) return; // 使用容器当前所在的文档创建编辑器容器,适配新窗口的document上下文 const editorContainer = container.ownerDocument.createElement("div"); container.appendChild(editorContainer); const quill = new Quill(editorContainer, { theme: "snow", readOnly, modules: { history: {}, toolbar: readOnly ? false : { container: [ ["bold", "italic", "underline", { header: 3 }], ], }, clipboard: { allowed: { tags: ["strong", "h3", "h4", "em", "p", "br", "span", "u"], attributes: [], }, customButtons: [], keepSelection: true, substituteBlockElements: true, magicPasteLinks: false, removeConsecutiveSubstitutionTags: false, }, }, }); ref.current = quill; if (defaultValue) { const delta = quill.clipboard.convert({ html: defaultValue }); quill.setContents(delta, "silent"); quill.history.clear(); } // 提取事件处理函数,方便后续清理 const handleTextChange = () => { if (quill.getLength() <= 1) { onTextChange(""); } else { onTextChange(quill.getSemanticHTML().replaceAll("<p></p>", "<p><br/></p>")); } }; quill.on(Quill.events.TEXT_CHANGE, handleTextChange); return () => { ref.current = null; // 移除事件监听,避免内存泄漏 quill.off(Quill.events.TEXT_CHANGE, handleTextChange); container.innerHTML = ""; }; }, [ref, readOnly, defaultValue, onTextChange]); // 添加所有相关依赖,确保props变化时重新初始化编辑器 return ( <div spellCheck={false} className={`ql-top-container ${readOnly ? "readonly" : ""} ${resize ? "resizable" : ""}`} ref={containerRef} ></div> ); };
额外优化:给新窗口注入Quill样式
新窗口的document未加载原页面的Quill CSS,可能导致编辑器样式丢失,可在NewWindowPortal中添加样式注入逻辑:
export const NewWindowPortal = ({ children }: { children: (props) => ReactNode }) => { const ref = useRef(null); const containerEl = useMemo(() => document.createElement("div"), []); useEffect(() => { const externalWindow = window.open("", "", "width=600,height=400,left=200,top=200"); if (!externalWindow) return; // 注入Quill Snow主题样式,替换为你项目中实际的样式路径 const styleLink = externalWindow.document.createElement('link'); styleLink.rel = 'stylesheet'; styleLink.href = 'https://cdn.quilljs.com/1.3.7/quill.snow.css'; externalWindow.document.head.appendChild(styleLink); externalWindow.document.body.appendChild(containerEl); // 窗口关闭时清理资源 const handleUnload = () => { containerEl.remove(); }; externalWindow.addEventListener('unload', handleUnload); return () => { externalWindow.removeEventListener('unload', handleUnload); externalWindow.close(); }; }, []); return ( <> <BodyShort>This is a new window</BodyShort> {createPortal(children({ ref }), containerEl)} </> ); };
内容的提问来源于stack exchange,提问作者Leff
相关产品推荐
相关产品推荐

