手把手打造高性能Markdown编辑器实战

春明 Dev 组件 阅读 2,191
赞 18 收藏
二维码
手机扫码查看
反馈

我为什么非得折腾 Markdown 编辑器?

说真的,每次做内容创作类的项目,最让我头疼的不是 UI,也不是数据流,而是那个看着简单、实则坑多的 Markdown 编辑器。你以为就是个文本框加个预览?错了。光是选型就能让你在三个主流方案之间来回摇摆:CodeMirror、SimpleMDE(现在叫 EasyMDE)、还有用 React 写的 react-markdown-editor-lite。这三个我都用过,踩过坑,也改过源码,今天就来唠点实在的。

手把手打造高性能Markdown编辑器实战

结论先放这儿:我现在基本只用 react-markdown-editor-lite,除非项目明确要求高度可定制的编辑行为,否则我不会再碰 CodeMirror 那种重型方案。下面细说原因。

谁更灵活?谁更省事?

先说 CodeMirror。这玩意儿确实老牌,功能强,支持各种语言高亮,甚至能当迷你 IDE 用。但问题是——你要的是一个 Markdown 编辑器,不是 VS Code。我之前在一个文档系统里强行集成 CodeMirror + Markdown mode,折腾了整整两天才把实时预览和工具栏搞通顺。代码长这样:

import CodeMirror from 'codemirror';
import 'codemirror/mode/markdown/markdown';
import 'codemirror/addon/display/placeholder';

const editor = CodeMirror.fromTextArea(document.getElementById('markdown-textarea'), {
  mode: 'markdown',
  lineNumbers: true,
  autofocus: true,
  extraKeys: {
    'Ctrl-Space': 'autocomplete'
  }
});

// 实时同步到预览区
editor.on('change', (cm) => {
  const markdownContent = cm.getValue();
  fetch('https://jztheme.com/api/preview', {
    method: 'POST',
    body: JSON.stringify({ content: markdownContent }),
    headers: { 'Content-Type': 'application/json' }
  }).then(res => res.text())
    .then(html => {
      document.getElementById('preview').innerHTML = html;
    });
});

看起来还行?但实际问题一堆:滚动同步难对齐、移动端输入卡顿、样式覆盖麻烦、引入后包体积直接涨了 100KB+。关键是,你只是想让用户写点带格式的文字,结果搞得像在开发编辑器插件。

再说 EasyMDE。它基于 CodeMirror 封装了一层,号称“开箱即用”,确实比裸用 CodeMirror 省事一点。初始化只要几行:

import EasyMDE from 'easymde';
import 'easymde/dist/easymde.min.css';

const easyMDE = new EasyMDE({
  element: document.getElementById('editor'),
  spellChecker: false,
  autosave: {
    enabled: true,
    uniqueId: 'post-1'
  }
});

但它的问题在于“封装得太死”。你想改个按钮顺序?不好意思,得自己 patch CSS 或者重写 toolbar 数组。想禁用某个默认快捷键?查了半天文档才发现要用 options.shortcuts 覆盖。而且它的自动保存功能有个 bug:如果用户没输内容就刷新,反而会存个空字符串进去,我为此加了防抖 + 非空判断才搞定。这里注意,我踩过好几次坑。

我的选择:react-markdown-editor-lite

直到我接手一个内部博客平台,时间紧任务重,实在不想再跟 CodeMirror 扯皮,试了下 react-markdown-editor-lite,发现真香。

首先它是为 React 生态设计的,用起来自然流畅。其次 API 极其简洁,核心配置就这几个:

import MdEditor from 'react-markdown-editor-lite';
import 'react-markdown-editor-lite/lib/index.css';
import MarkdownIt from 'markdown-it';

const mdParser = new MarkdownIt();

function App() {
  const handleEditorChange = ({ text }) => {
    console.log('Markdown Text:', text);
  };

  return (
    <MdEditor
      value=""
      style={{ height: '500px' }}
      renderHTML={(text) => mdParser.render(text)}
      onChange={handleEditorChange}
      placeholder="写点什么吧..."
      config={{
        view: { menu: true, md: true, html: false },
        canView: { menu: true, md: true, html: false, fullScreen: true }
      }}
    />
  );
}

重点来了:它不依赖 CodeMirror!用的是原生 contenteditable + 自定义解析逻辑,所以体积小、启动快、移动端体验也好很多。我测过,在安卓机上打字基本不卡,而 EasyMDE 在低端机上经常掉帧。

而且扩展性强。比如我想加一个 @ 提及功能,直接可以在 toolbar 里插入自定义按钮:

config={{
  view: { menu: true, md: true },
  toolbar: [
    'bold', 'italic', 'heading',
    {
      name: 'mention',
      action: () => alert('插入@提及'),
      className: 'toolbar-icon mention-icon',
      text: '@'
    }
  ]
}}

虽然它不像 CodeMirror 那样支持语法树级别的操作,但对于大多数 CMS、笔记、评论场景来说,完全够用。改完后仍有一两个小问题,比如表格输入不够顺滑,但无大碍。

性能对比:差距比我想象的大

我把三个方案都打包进同一个项目,做了个简单的性能测试(页面加载时间 + 初始渲染耗时 + 滚动流畅度):

  • CodeMirror:首屏加载慢约 800ms(gzip 后 JS 多出 120KB),编辑区域首次渲染 300ms+
  • EasyMDE:略优于 CodeMirror,但本质还是同一套内核,优化有限
  • react-markdown-editor-lite:gzip 后仅增加 45KB,初始渲染不到 100ms,滚动顺滑

别小看这几百毫秒,用户感知很明显。特别是我们有个需求是嵌入式评论框,必须轻量。最后我用 react-markdown-editor-lite 做了个 mini 版,高度压缩到 200px,工具栏精简到只剩粗体和链接,用户体验反而更好。

我的选型逻辑

我现在选 Markdown 编辑器,优先看三点:

  1. 是否与当前技术栈契合:React 项目首选 react 组件,Vue 就选 Vue 兼容的,别硬套 vanilla JS 方案。
  2. 体积和加载速度:超过 80KB 的库我会慎重考虑,尤其面向 C 端用户。
  3. 自定义成本:能不能快速改 toolbar?能不能关掉不需要的功能?有没有清晰的事件钩子?

按这个标准,react-markdown-editor-lite 完胜。当然,如果你要做的是类似 Obsidian 的本地笔记应用,需要分屏、大纲、语法高亮、LaTeX 支持全套功能,那还是得上 CodeMirror 或者 ProseMirror 这种重型方案。但那种情况毕竟是少数。

说到底,这个方案不是最优的,但最简单。对于大部分项目来说,够用 + 稳定 + 快速上线,比“理论上更强大”重要得多。

踩坑提醒:这三点一定注意

不管用哪个方案,这几个坑我都踩过,提前告诉你:

  • 预览 XSS 风险:别直接用 innerHTML 渲染 markdown 输出,一定要用 DOMPurify 或 marked + sanitize 配置处理。
  • 移动端光标错乱:contenteditable 在 iOS 上偶尔失焦,解决方案是手动 focus + selection restore,亲测有效。
  • 中文输入法兼容性:监听 input 事件时,IME 输入过程中会频繁触发,要用 compositionstart/end 来过滤。

尤其是第三点,我在 react-markdown-editor-lite 里发现它没处理 IME,导致拼音还没上屏就被当成内容更新了。后来我自己提了个 PR 加了判断:

if (isComposing) return;
// 正常触发 onChange

改完后体验提升明显。这也说明,轻量库虽然功能少,但更容易改源码,出了问题也能自己修,不像某些大库改一行等于重构整个模块。

以上是我的对比总结,有更优的实现方式欢迎评论区交流

这几种方案我都实战过,各有适用场景。但我现在的默认选项就是 react-markdown-editor-lite,简单、够用、维护成本低。除非业务有特殊需求,否则我不会再轻易尝试从零搭建。

这类组件看似不起眼,但一旦选错,后期维护代价很高。希望我这些踩坑经验能帮你少走点弯路。这个技巧的拓展用法还有很多,后续会继续分享这类实战总结。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论