从引入 wangEditor 的一次思考:React 下富文本编辑实战与深入解析

582 阅读3分钟

我是彭于晏,我爱编程,我喂自己袋盐。
本文适用读者:具备 React/TypeScript 基础,正在为后台管理或 CMS 系统集成富文本编辑器,希望了解 forwardRef / useImperativeHandle 高阶用法的前端开发者。


image.png

1 项目背景与技术选型

需求场景:后台需要配置一段富文本(带样式/超链接/占位标签),在 Web 前台通过 innerHTML 渲染。

核心诉求

  1. 编辑体验轻量、上手快;
  2. API 灵活,可扩展特殊标签;
  3. 依赖体积可控,社区活跃。

综合权衡后,笔者选择了 wangEditor V5。它支持 Vue3 & React 双生态,打包后 ~100 KB(gzip),足够轻巧,同时社区文档完善。


2 快速集成 wangEditor

2.1 安装依赖

# React 项目
pnpm add @wangeditor/editor @wangeditor/editor-for-react
# or yarn / npm 同理

⚠️ 注意:Vue 生态需替换为 @wangeditor/editor @wangeditor/editor-for-vue@next

2.2 引入样式

import "@wangeditor/editor/dist/css/style.css";

2.3 最小可运行示例

import { useState } from "react";
import { Editor, Toolbar } from "@wangeditor/editor-for-react";

export default function Demo() {
  const [html, setHtml] = useState("<p>hello world</p>");
  return (
    <>
      <Toolbar editor={null} />
      <Editor value={html} onChange={(ed) => setHtml(ed.getHtml())} />
    </>
  );
}

✅ 至此,一个可用的富文本编辑器已跑通。


3 业务级封装:打造 <RichTextEditor /> 组件

在真实项目中,我们希望:

  • 受控:由 Form 管理表单值;
  • 解耦:父组件可直接调用编辑器实例方法(如 getHtmlinsertText)。

3.1 forwardRef + useImperativeHandle 双剑合璧

import {
  forwardRef,
  useEffect,
  useState,
  useImperativeHandle,
} from "react";
import { Editor, Toolbar } from "@wangeditor/editor-for-react";
import type { IDomEditor } from "@wangeditor/editor";
import "@wangeditor/editor/dist/css/style.css";

interface Props {
  value?: string;
  onChange?: (val: string) => void;
}

const RichTextEditor = forwardRef<IDomEditor | null, Props>(
  ({ value = "", onChange }, ref) => {
    const [editor, setEditor] = useState<IDomEditor | null>(null);

    // 组件卸载时销毁实例,避免内存泄漏
    useEffect(() => () => editor?.destroy(), [editor]);

    // 🔥 关键:对外暴露 editor 方法 / 属性
    useImperativeHandle(ref, () => editor as IDomEditor, [editor]);

    return (
      <div className="border rounded shadow-sm">
        <Toolbar editor={editor} />
        <Editor
          value={value}
          onCreated={setEditor}
          onChange={(ed) => onChange?.(ed.getHtml())}
          defaultConfig={{ placeholder: "请输入内容…" }}
          style={{ maxHeight: 300, overflowY: "auto" }}
        />
      </div>
    );
  }
);

export default RichTextEditor;

要点回顾

  • forwardRef 让函数组件也能拿到外部 ref
  • useImperativeHandle 决定 ref.current 暴露哪些方法,减少不必要的实现细节泄漏。
  • 记得在 useEffect 清理卸载 editor.destroy(),否则多次进入页面会残留 DOM。

4 父组件实战:表单联动 + 获取 HTML

import { Form, Row } from "antd";
import { useRef } from "react";
import RichTextEditor from "@/components/RichTextEditor";

export default function Create() {
  const [form] = Form.useForm();
  const editorRef = useRef<IDomEditor | null>(null);

  const handleGetHtml = () => {
    console.log("当前 HTML 内容:", editorRef.current?.getHtml());
  };

  return (
    <Form form={form} layout="vertical">
      <Row gutter={24}>
        <Form.Item
          name="task_desc_en"
          label="任务描述(英)"
          rules={[{ required: true, message: "任务描述必填" }]}
        >
          <RichTextEditor ref={editorRef} />
        </Form.Item>
      </Row>
      {/* 省略其它表单项 */}
    </Form>
  );
}

小结

  • 调用 editorRef.current?.getHtml() 可获完整 HTML;
  • 所有表单字段依旧走 AntD 的校验链路,不破坏统一提交流程。

image.png


5 API 深挖:从 Console 到官方文档

很多同学喜欢在控制台打印 editorRef.current 来“摸索” API:

方法原型可能用途说明
getHtml() / getText()取内容最常用
insertText(text)插入文本光标处插入纯文本
setHtml(html)设置整块 HTML批量替换内容
addMark(type, value)设置样式如加粗、斜体、自定义 mark

然而 源码已压缩,过度反向工程既浪费时间,也容易踩坑。建议顺序

  1. 先浏览 Console 了解实例结构;
  2. 立即回到 官方 API 查找对应方法;
  3. 若要二次开发(扩展菜单 / 自定义上传),阅读源码或 Issues。

新手问老手,我没用过,怎么用啊。老手:???,看文档。个人认为学会看文档真的很重要,ai给的东西会有滞后性。


6 封装心得与最佳实践

主题建议
组件解耦仅暴露必须的 API,避免泄漏内部状态;
性能优化使用 lazy import + destroy,在需要时再加载编辑器;
样式隔离通过 BEM / CSS Modules 防止编辑器默认样式污染业务页面;

7 结语

"第三方库只是工具,文档才是真正的老师。 "

这一次集成 wangEditor 的过程,再次印证了 “读文档 > 盲试 API” 的简单真理。同时,forwardRef + useImperativeHandle 让函数组件也能优雅地对外暴露实例方法,为未来的可维护性买下了保险。

如果这篇文章对你有所启发,点赞 / 关注 是对我最大的鼓励!

参考链接