集成方式
React / Next.js 集成
在 React 与 Next.js App Router 中挂载 SVeditor,稳定 options、自动保存与 SSR 安全边界。
React / Next.js 集成
三条规则:只在客户端创建、保持稳定的 options 引用、卸载时 destroy()。
前置:ESM 包已安装。示例基于 Next.js App Router;其他 React 框架可忽略路由相关说明。
SSR 与 ESM 包
ESM 构建可能在 import 阶段访问浏览器全局对象。
- 从 client 模块
import "@wztlink1013/sveditor/style.css"。 - 仅在浏览器加载
@wztlink1013/sveditor—next/dynamic+ssr: false,或在useEffect中动态import()。
详见 ESM 集成 — Next.js。
可复用的挂载组件
将下列模式复制到你的项目(例如 components/sveditor-mount.tsx):
"use client";
import { useEffect, useRef } from "react";
import { SVeditor } from "@wztlink1013/sveditor";
import type { EditorOptions } from "@wztlink1013/sveditor";
import { cn } from "@/lib/utils"; // 你的 className 工具
import "@wztlink1013/sveditor/style.css";
export type SveditorMountProps = {
className?: string;
options: Omit<EditorOptions, "el">;
};
export function SveditorMount({ className, options }: SveditorMountProps) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const editor = SVeditor.create({ el, ...options });
return () => editor.destroy();
}, [options]);
return (
<div ref={containerRef} className={cn("min-h-[640px] w-full", className)} />
);
}App Router 中可通过 next/dynamic(..., { ssr: false }) 从页面 client 包装层导出。
用 useMemo 稳定 options
每次 render 新建 options 会重建编辑器,导致丢焦点或重复 onFetch。
"use client";
import { useMemo } from "react";
import type { EditorOptions } from "@wztlink1013/sveditor";
import { SveditorMount } from "@/components/sveditor-mount";
type EditorInitOptions = Omit<EditorOptions, "el">;
export function DocEditor({ docId }: { docId: string }) {
const options = useMemo<EditorInitOptions>(
() => ({
extensionsOptions: {
autoSave: {
onFetch: async () => {
const res = await fetch(`/api/docs/${docId}`, { cache: "no-store" });
const data = await res.json();
return { content: data.content ?? "" };
},
onSaveContent: async (json) => {
await fetch(`/api/docs/${docId}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ content: json }),
});
},
debounceMs: 1000,
},
},
}),
[docId],
);
return <SveditorMount options={options} />;
}自动保存 + 元数据:自动保存指南。
Server Component 页面
页面保持 Server Component,向 client 子组件传参即可:
import { DocEditor } from "@/components/doc-editor";
export default async function DocPage({
params,
}: {
params: Promise<{ docId: string }>;
}) {
const { docId } = await params;
return <DocEditor docId={docId} />;
}懒加载(可选)
import dynamic from "next/dynamic";
const DocEditor = dynamic(
() => import("@/components/doc-editor").then((m) => m.DocEditor),
{ ssr: false, loading: () => <p>Loading editor...</p> },
);常见模式
自动保存 + 元数据
extensionsOptions: {
autoSave: {
onFetch: () => fetchContent(docId),
onSaveContent: (json) => saveContent(docId, json),
},
meta: {
onFetchMeta: () => fetchMeta(docId),
onSaveMeta: (meta) => saveMeta(docId, meta),
},
}不要从 autoSave.onFetch 返回 meta。
自定义通知
extensionsOptions: {
notifications: {
toast: myToastAdapter,
builtinToaster: false, // 仅当全局未挂载 Sonner 时设为 true
},
}检查清单
| 做法 | 原因 |
|---|---|
挂载组件加 "use client" | SDK 仅浏览器运行 |
useMemo([docId, ...]) | 文档 id 变化时重建 |
全局或挂载模块导入 style.css | 布局依赖样式 |
effect cleanup 中 destroy() | 避免泄漏 |
注意事项
- 不要在 Server Component 中调用
SVeditor.create()。 docId变化时加入useMemo依赖。- 在线完整示例见 Playground。