React
本指南描述了如何将 Tiptap 与您的 React 项目集成。我们使用 Vite,但其他设置的工作流程应该类似。
创建一个 React 项目(可选)
以一个名为 my-tiptap-project 的新 React 项目开始。 Vite 将设置我们所需的一切。
# 使用 npm 创建项目
npm create vite@latest my-tiptap-project -- --template react-ts
# 或者,使用 pnpm 创建项目
pnpm create vite@latest my-tiptap-project --template react-ts
# 或者,使用 yarn 创建项目
yarn create vite my-tiptap-project --template react-ts
# 进入目录
cd my-tiptap-project安装依赖
接下来,安装 @tiptap/react 包,@tiptap/pm(ProseMirror 库)和 @tiptap/starter-kit,它包括启动时常用的扩展。
- @tiptap/react:Tiptap 的 React 绑定,包括 Tiptap 的核心功能。
- @tiptap/pm:Tiptap 的 ProseMirror 依赖,是编辑器正常工作的必需库。
- @tiptap/starter-kit:一组常用扩展,提供段落、标题、加粗、斜体等基本功能。
npm install @tiptap/react @tiptap/pm @tiptap/starter-kit如果您按照步骤 1 和 2 操作,现在可以通过 npm run dev 启动项目,并在浏览器中打开 http://localhost:3000。
集成 Tiptap
New: React Composable API
Tiptap 现在提供了一个声明式的 <Tiptap> 组件,具有自动上下文管理和内置子组件。非常适合拥有多个子组件的复杂 UI。了解更多 →
要开始使用 Tiptap,创建一个新组件。我们称之为 Tiptap,并在 src/Tiptap.tsx 中添加以下代码:
// src/Tiptap.tsx
import { useEditor, EditorContent } from '@tiptap/react'
import { FloatingMenu, BubbleMenu } from '@tiptap/react/menus'
import StarterKit from '@tiptap/starter-kit'
const Tiptap = () => {
const editor = useEditor({
extensions: [StarterKit], // 定义您的扩展数组
content: '<p>Hello World!</p>', // 初始内容
})
return (
<>
<EditorContent editor={editor} />
<FloatingMenu editor={editor}>这是浮动菜单</FloatingMenu>
<BubbleMenu editor={editor}>这是气泡菜单</BubbleMenu>
</>
)
}
export default Tiptap将其添加到您的应用中
最终,替换 src/App.tsx 的内容为新的 Tiptap 组件。
import Tiptap from './Tiptap'
const App = () => {
return (
<div className="card">
<Tiptap />
</div>
)
}
export default App在子组件中消费编辑器上下文
Tiptap 提供了一个名为 EditorContext 的 React 上下文,允许您在组件树的任何位置访问编辑器实例及其状态。这对于构建自定义工具栏、菜单或其他需要与编辑器交互的组件非常有用。
// src/Tiptap.tsx
import { useEditor, EditorContent, EditorContext } from '@tiptap/react'
import { FloatingMenu, BubbleMenu } from '@tiptap/react/menus'
import StarterKit from '@tiptap/starter-kit'
import { useMemo } from 'react'
const Tiptap = () => {
const editor = useEditor({
extensions: [StarterKit], // 定义扩展数组
content: '<p>Hello World!</p>', // 初始内容
})
// 缓存 Provider 的值,避免不必要的重新渲染
const providerValue = useMemo(() => ({ editor }), [editor])
return (
<EditorContext.Provider value={providerValue}>
<EditorContent editor={editor} />
<FloatingMenu editor={editor}>这是浮动菜单</FloatingMenu>
<BubbleMenu editor={editor}>这是气泡菜单</BubbleMenu>
</EditorContext.Provider>
)
}
export default Tiptap在子组件中使用编辑器上下文
如果您使用 EditorContext.Provider 来设置 Tiptap 编辑器,现在可以在任意子组件中通过 useCurrentEditor 钩子访问编辑器实例。
import { useCurrentEditor } from '@tiptap/react'
const EditorJSONPreview = () => {
const { editor } = useCurrentEditor()
return <pre>{JSON.stringify(editor.getJSON(), null, 2)}</pre>
}重要:如果您使用 useEditor 钩子来设置您的编辑器,则此方法无效。
您现在应该在浏览器中看到一个非常基础的 Tiptap 示例。
添加开始或结束插槽
由于 EditorContent 组件是由 EditorProvider 组件渲染的,因此我们现在无法直接定义在编辑器内容之前或之后渲染的位置。为此,我们可以在 EditorProvider 组件上使用 slotBefore 和 slotAfter 属性。
<EditorProvider
extensions={extensions}
content={content}
slotBefore={<MyEditorToolbar />}
slotAfter={<MyEditorFooter />}
/>容器属性
EditorProvider 组件接受一个 editorContainerProps 属性来传递属性到编辑器提供器的容器元素。
<EditorProvider
extensions={extensions}
content={content}
editorContainerProps={{ className: 'editor-container' }}
/>响应编辑器状态变化
要响应编辑器状态变化,您可以使用 @tiptap/react 提供的 useEditorState 钩子。该钩子可用于从编辑器状态中获取信息,而不会导致编辑器组件或其子组件重新渲染。
import { useEditorState } from '@tiptap/react'
function MyEditorComponent() {
// ... 您的编辑器设置代码
const editorState = useEditorState({
editor,
// selector 函数用于选择您想要响应的状态
selector: ({ editor }) => {
if (!editor) return null;
return {
isEditable: editor.isEditable,
currentSelection: editor.state.selection,
currentContent: editor.getJSON(),
// 您可以添加更多状态属性,例如:
// isBold: editor.isActive('bold'),
// isItalic: editor.isActive('italic'),
};
},
});
}提示
使用 <Tiptap> 组件时,优先使用 useTiptapState,它会自动使用上下文中的编辑器实例。
在 React 中使用 Tiptap 进行 SSR
Tiptap 可以与 React 应用中的服务端渲染(SSR)一起使用。然而,为确保编辑器仅在客户端初始化,您需要在创建编辑器实例时使用 immediatelyRender 选项,防止它在服务器端渲染。
以下是一个设置 React 组件中 Tiptap SSR 的示例:
'use client'
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
export function MyEditor() {
const editor = useEditor({
extensions: [StarterKit],
content: '<p>Hello World!</p>',
// 禁用即时渲染以避免 SSR 问题
immediatelyRender: false,
})
if (!editor) {
return null // 确保编辑器初始化前不渲染
}
return <EditorContent editor={editor} />
}Tiptap.Loading 组件在 SSR 中非常有用,它会在编辑器于客户端初始化前显示占位内容。
优化您的性能
我们建议您访问React 性能指南,以高效集成 Tiptap 编辑器。这将帮助您避免应用规模扩大时可能出现的问题。
另一种方案:使用 EditorContent 手动设置
对于需要更控制力的场景,可以直接使用 EditorContent:
// src/Editor.tsx
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
function Editor() {
const editor = useEditor({
extensions: [StarterKit], // 定义您的扩展数组
content: '<p>Hello World!</p>', // 初始内容
})
return <EditorContent editor={editor} />
}
export default Editor此方法要求手动为每个组件传入 editor 属性。大多数情况下推荐使用 <Tiptap> 组件,它减少了模板代码并自动提供上下文。
API 参考
Tiptap 组件
根提供者组件,通过 React 上下文使编辑器实例可用。
| 参数 | 类型 | 描述 |
|---|---|---|
instance | Editor | null | 来自 useEditor() 的编辑器实例 |
children | ReactNode | 子组件 |
useTiptap 钩子
返回 Tiptap 的上下文值。
const { editor, isReady } = useTiptap()| 属性 | 类型 | 描述 |
|---|---|---|
editor | Editor | null | 编辑器实例 |
isReady | boolean | 编辑器是否完成初始化 |
useTiptapState 钩子
使用选择器函数订阅编辑器状态的一部分。
const value = useTiptapState(selector, equalityFn?)| 参数 | 类型 | 描述 |
|---|---|---|
selector | (state: EditorStateSnapshot) => T | 状态选择函数 |
equalityFn | (a: T, b: T) => boolean | 可选的比较函数,控制重新渲染 |