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

如何使用useEffect为Tiptap预览模态框添加/移除事件监听器

解决Tiptap预览模态框事件监听问题

核心问题分析

  1. ref绑定在了DialogContent上,但实际需要监听的是内部动态生成的预览内容容器,导致无法获取目标元素
  2. 仅以open作为useEffect依赖,无法保证模态框DOM完全渲染后再执行事件绑定,此时ref.current可能为空
  3. 预览内容随编辑器动态变化,需要在内容更新时重新绑定监听器

修复方案

1. 调整ref绑定目标

把ref直接绑定到预览内容的div上,确保能精准获取到要监听的元素。

2. 优化useEffect的依赖与执行时机

  • 增加editor.getHTML()作为依赖,确保内容变化时重新绑定监听器
  • 使用useLayoutEffect(或在useEffect中通过requestAnimationFrame确保DOM渲染完成),避免DOM未挂载时绑定失败
  • 完善监听器清理逻辑,每次绑定前先移除旧监听器,防止重复绑定

3. 修正onOpenChange函数写法

当前嵌套函数写法会导致逻辑执行异常,改为直接接收open参数的函数。

完整修复代码

type Props = {
  editor: Editor;
};

export const PreviewButton = ({ editor }: Props) => {
  const pathname = usePathname();
  const [open, setOpen] = useState(false);
  const previewRef = useRef<HTMLDivElement>(null); // 绑定预览内容容器的ref

  const handleClick = () => {
    console.log(pathname);
    setOpen(true);
    return true;
  };

  useLayoutEffect(() => {
    if (!open || !previewRef.current) return;

    const handlePreviewClick = () => console.log('Hi');
    const previewContent = previewRef.current;
    
    // 绑定点击监听器
    previewContent.addEventListener('click', handlePreviewClick);

    // 清理函数:移除监听器,避免内存泄漏
    return () => {
      previewContent.removeEventListener('click', handlePreviewClick);
    };
  }, [open, editor.getHTML()]); // 依赖open和编辑器内容,确保内容变化时重新绑定

  const onOpenChange = (isOpen: boolean) => {
    editor.setEditable(!isOpen);
    setOpen(isOpen);
  };

  const modalContent = (
    <DialogContent>
      <DialogHeader>
        <DialogTitle>Podgląd</DialogTitle>
      </DialogHeader>
      {open && (
        <div
          ref={previewRef}
          style={{ maxWidth: '800px' }}
          className='ProseMirror prose dark:prose-invert'
          dangerouslySetInnerHTML={{ __html: editor.getHTML() }}
        />
      )}
    </DialogContent>
  );

  return (
    <MenuButton
      icon={<PreviewIcon />}
      command={handleClick}
      tooltip={{
        content: <TooltipContent title={_t('commands.preview')} />,
      }}
      modal={{
        isModalButton: true,
        modalContent: modalContent,
        isOpen: open,
        setIsOpen: setOpen,
        onOpenChange: onOpenChange,
      }}
    />
  );
};

额外说明

  • useLayoutEffect会在DOM更新完成后立即执行,确保previewRef.current能获取到已挂载的元素
  • 依赖editor.getHTML()保证每次编辑器内容变化、模态框重新打开时,监听器都会绑定到最新的内容上
  • 移除了lastContent状态,模态框打开时直接渲染最新的编辑器内容即可,无需缓存旧内容

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 22:41:25