我是彭于晏,我爱编程,我喂自己袋盐。
本文适用读者:具备 React/TypeScript 基础,正在为后台管理或 CMS 系统集成富文本编辑器,希望了解forwardRef/useImperativeHandle高阶用法的前端开发者。
1 项目背景与技术选型
需求场景:后台需要配置一段富文本(带样式/超链接/占位标签),在 Web 前台通过
innerHTML渲染。核心诉求:
- 编辑体验轻量、上手快;
- API 灵活,可扩展特殊标签;
- 依赖体积可控,社区活跃。
综合权衡后,笔者选择了 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管理表单值; - 解耦:父组件可直接调用编辑器实例方法(如
getHtml、insertText)。
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 的校验链路,不破坏统一提交流程。
5 API 深挖:从 Console 到官方文档
很多同学喜欢在控制台打印 editorRef.current 来“摸索” API:
| 方法原型 | 可能用途 | 说明 |
|---|---|---|
getHtml() / getText() | 取内容 | 最常用 |
insertText(text) | 插入文本 | 光标处插入纯文本 |
setHtml(html) | 设置整块 HTML | 批量替换内容 |
addMark(type, value) | 设置样式 | 如加粗、斜体、自定义 mark |
然而 源码已压缩,过度反向工程既浪费时间,也容易踩坑。建议顺序:
- 先浏览 Console 了解实例结构;
- 立即回到 官方 API 查找对应方法;
- 若要二次开发(扩展菜单 / 自定义上传),阅读源码或 Issues。
新手问老手,我没用过,怎么用啊。老手:???,看文档。个人认为学会看文档真的很重要,ai给的东西会有滞后性。
6 封装心得与最佳实践
| 主题 | 建议 |
|---|---|
| 组件解耦 | 仅暴露必须的 API,避免泄漏内部状态; |
| 性能优化 | 使用 lazy import + destroy,在需要时再加载编辑器; |
| 样式隔离 | 通过 BEM / CSS Modules 防止编辑器默认样式污染业务页面; |
7 结语
"第三方库只是工具,文档才是真正的老师。 "
这一次集成 wangEditor 的过程,再次印证了 “读文档 > 盲试 API” 的简单真理。同时,forwardRef + useImperativeHandle 让函数组件也能优雅地对外暴露实例方法,为未来的可维护性买下了保险。
如果这篇文章对你有所启发,点赞 / 关注 是对我最大的鼓励!
参考链接
- wangEditor 官网:www.wangeditor.com/