---
title: "React 绑定"
description: "使用 @hocuspocus/provider-react 通过组件和 hooks 将 Hocuspocus 接入 React 应用。"
canonical_url: "https://tiptap.zhcndoc.com/hocuspocus/provider/react"
---

# React 绑定

使用 @hocuspocus/provider-react 通过组件和 hooks 将 Hocuspocus 接入 React 应用。

自 v4 起，Hocuspocus 通过 [`@hocuspocus/provider-react`](https://www.npmjs.com/package/@hocuspocus/provider-react) 包提供了专门的 React 绑定。它提供了两个用于管理 WebSocket 连接和房间生命周期的组件，以及用于订阅 provider 状态（连接、同步、awareness、事件）的 hooks——全部基于 `useSyncExternalStore` 构建，并且对 React StrictMode 安全。

## 安装

```bash
npm install @hocuspocus/provider @hocuspocus/provider-react yjs
```

Peer dependencies：React 18 或 19，Yjs `^13.6.8`，以及与之匹配版本的 `@hocuspocus/provider`。

## 快速开始

使用 `HocuspocusProviderWebsocketComponent`（它管理共享的 WebSocket）和一个或多个 `HocuspocusRoom` 组件（每个文档一个）包裹你的协作子树。在房间内部，像 `useHocuspocusProvider`、`useHocuspocusAwareness` 和 `useHocuspocusConnectionStatus` 这样的 hooks 可以让你访问 provider。

```tsx
import {
  HocuspocusProviderWebsocketComponent,
  HocuspocusRoom,
} from '@hocuspocus/provider-react'

export function App() {
  return (
    <HocuspocusProviderWebsocketComponent url="ws://127.0.0.1:1234">
      <HocuspocusRoom name="example-document">
        <Editor />
      </HocuspocusRoom>
    </HocuspocusProviderWebsocketComponent>
  )
}
```

websocket 组件会为其子树创建一个单独的 `HocuspocusProviderWebsocket`。多个 `HocuspocusRoom` 可以共享它，这样在文档之间切换时就能避免连接开销（也就是多路复用）。

## 组件

### HocuspocusProviderWebsocketComponent

管理共享的 `HocuspocusProviderWebsocket` 实例。在应用顶部附近创建一次即可。

```tsx
<HocuspocusProviderWebsocketComponent url="ws://127.0.0.1:1234">
  {children}
</HocuspocusProviderWebsocketComponent>
```

**Props**

| Prop                | Type                          | Description                                          |
| ------------------- | ----------------------------- | ---------------------------------------------------- |
| `url`               | `string`                      | Hocuspocus 服务器 URL。除非提供了 `websocketProvider`，否则为必需项。 |
| `websocketProvider` | `HocuspocusProviderWebsocket` | 提供你自己的 socket 实例以获得完全控制。与 `url` 互斥。                  |
| `children`          | `ReactNode`                   | 通常是一个或多个 `HocuspocusRoom`。                           |

该组件会优雅地处理 React StrictMode 的双重挂载——WebSocket 每个挂载周期只创建一次，并且只有在不是外部提供时才会销毁。

### HocuspocusRoom

创建一个文档专属的 `HocuspocusProvider`，并将其连接到共享的 WebSocket。必须渲染在 `HocuspocusProviderWebsocketComponent` 内部。

```tsx
<HocuspocusRoom
  name="example-document"
  token={async () => fetchJwt()}
  onAuthenticationFailed={(data) => console.error(data.reason)}
  onSynced={() => console.log('已同步')}
>
  <Editor />
</HocuspocusRoom>
```

**Props**

| Prop                                                                                                                                                                                                                                               | Type                                                  | Description                                                                                                                                                                  |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`                                                                                                                                                                                                                                             | `string`                                              | 文档名称。必需。                                                                                                                                                                     |
| `document`                                                                                                                                                                                                                                         | `Y.Doc`                                               | 可选——传入你自己的 Y.Doc。若省略，则会为你创建一个。                                                                                                                                               |
| `token`                                                                                                                                                                                                                                            | `string \| (() => string) \| (() => Promise<string>)` | 发送到服务器用于身份验证的 JWT（或异步解析器）。                                                                                                                                                   |
| `sessionAwareness`                                                                                                                                                                                                                                 | `boolean`                                             | 启用会话感知的多路复用，使具有相同文档名称的多个 room 可以共享一个 WebSocket。仅在连接到 v4 服务器时设为 `true`。默认 `false`。请参见 [`sessionAwareness`](https://tiptap.zhcndoc.com/hocuspocus/provider/configuration.md)。  |
| `flushDelay`                                                                                                                                                                                                                                       | `false \| number`                                     | 在短时间窗口内批量发送文档和 awareness 更新（毫秒），以在高强度编辑时减少 websocket 流量。默认 `false`。请参见[批量发送外发更新](https://tiptap.zhcndoc.com/hocuspocus/provider/configuration.md#batching-outgoing-updates)。 |
| `onOpen`, `onConnect`, `onClose`, `onDisconnect`, `onStatus`, `onSynced`, `onUnsyncedChanges`, `onMessage`, `onOutgoingMessage`, `onStateless`, `onAuthenticated`, `onAuthenticationFailed`, `onAwarenessUpdate`, `onAwarenessChange`, `onDestroy` | Function                                              | 每个[provider 事件](https://tiptap.zhcndoc.com/hocuspocus/provider/events.md)的可选处理器。                                                                                             |

更改 `name`、`document`、`token` 或上游 websocket provider 会重新创建底层 provider。其余内容都会保持稳定。

### 处理身份验证失败

当服务器的 `onAuthenticate` 钩子抛出异常时，provider 会发出 `authenticationFailed` 事件，并调用 `HocuspocusRoom` 上的 `onAuthenticationFailed` 处理器，参数为：

```ts
{ reason: string }  // 服务器抛出的错误消息
```

典型的用户体验：清除过期 token，显示登录界面，然后用新的 token 重新渲染。

```tsx
<HocuspocusRoom
  name="example-document"
  token={token}
  onAuthenticationFailed={({ reason }) => {
    console.warn('认证失败：', reason)
    setToken(null)  // 进入你的登录流程
  }}
>
  <Editor />
</HocuspocusRoom>
```

如果你更喜欢在嵌套组件中响应它，同样可以通过 [`useHocuspocusEvent('authenticationFailed', handler)`](#usehocuspocusevent) 使用该事件。

## Hooks

All hooks below must be used inside `HocuspocusRoom`. If there is no room context, they will throw an error.

### useHocuspocusProvider

Returns the current room's `HocuspocusProvider` instance. Use it when you need direct access to the provider—for example, to connect Tiptap's `Collaboration` / `CollaborationCaret` extensions to the provider's Y.Doc and awareness.

```tsx
import { useHocuspocusProvider } from '@hocuspocus/provider-react'
import { useEditor, EditorContent } from '@tiptap/react'
import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCaret from '@tiptap/extension-collaboration-caret'
import { StarterKit } from '@tiptap/starter-kit'

function Editor() {
  const provider = useHocuspocusProvider()

  const editor = useEditor({
    extensions: [
      StarterKit.configure({ undoRedo: false }),
      Collaboration.configure({ document: provider.document }),
      CollaborationCaret.configure({
        provider,
        user: { name: 'John Doe', color: '#ffcc00' },
      }),
    ],
  })

  return <EditorContent editor={editor} />
}
```

### useHocuspocusConnectionStatus

Subscribes to the WebSocket connection status. Returns `'connecting' | 'connected' | 'disconnected'`.

```tsx
import { useHocuspocusConnectionStatus } from '@hocuspocus/provider-react'

function ConnectionIndicator() {
  const status = useHocuspocusConnectionStatus()

  return (
    <div className={`status-${status}`}>
      {status === 'connected'
        ? '在线'
        : status === 'connecting'
          ? '正在连接…'
          : '离线'}
    </div>
  )
}
```

### useHocuspocusSyncStatus

Subscribes to whether local changes have been synced with the server. Returns `'synced' | 'syncing'`.

```tsx
import { useHocuspocusSyncStatus } from '@hocuspocus/provider-react'

function SaveIndicator() {
  const syncStatus = useHocuspocusSyncStatus()

  return <div>{syncStatus === 'syncing' ? '正在保存…' : '所有更改已保存'}</div>
}
```

### useHocuspocusAwareness

Subscribes to the list of connected users (awareness state). Returns an array of objects, each containing `clientId`, plus any state you set on awareness (name, color, cursor, etc.).

```tsx
import { useHocuspocusAwareness } from '@hocuspocus/provider-react'

function UserList() {
  const users = useHocuspocusAwareness()

  return (
    <div className="avatars">
      {users.map((user) => (
        <div
          key={user.clientId}
          style={{ backgroundColor: user.color as string }}
          title={user.name as string}
        >
          {(user.name as string | undefined)?.[0]}
        </div>
      ))}
    </div>
  )
}
```

### useHocuspocusEvent

Listen to any [provider event](https://tiptap.zhcndoc.com/hocuspocus/provider/events.md) via a stable subscription (the handler reference is kept up to date internally, so you don't need to memoize it).

```tsx
import { useHocuspocusEvent } from '@hocuspocus/provider-react'

function AuthGuard() {
  useHocuspocusEvent('authenticationFailed', (data) => {
    console.error('认证失败：', data.reason)
    redirectToLogin()
  })

  useHocuspocusEvent('close', (data) => {
    console.log('连接已关闭', data.event.code, data.event.reason)
  })

  return null
}
```

## 完整示例 — Tiptap + awareness

将这些全部组合起来：一个 Tiptap 编辑器、一个连接指示器和一个用户列表：

```tsx
import {
  HocuspocusProviderWebsocketComponent,
  HocuspocusRoom,
  useHocuspocusAwareness,
  useHocuspocusConnectionStatus,
  useHocuspocusProvider,
} from '@hocuspocus/provider-react'
import { EditorContent, useEditor } from '@tiptap/react'
import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCaret from '@tiptap/extension-collaboration-caret'
import { StarterKit } from '@tiptap/starter-kit'

function Editor() {
  const provider = useHocuspocusProvider()
  const status = useHocuspocusConnectionStatus()
  const users = useHocuspocusAwareness()

  const editor = useEditor({
    extensions: [
      StarterKit.configure({ undoRedo: false }),
      Collaboration.configure({ document: provider.document }),
      CollaborationCaret.configure({
        provider,
        user: { name: 'John Doe', color: '#ffcc00' },
      }),
    ],
  })

  return (
    <>
      <header>
        <span>状态：{status}</span>
        <span>{users.length} 在线</span>
      </header>
      <EditorContent editor={editor} />
    </>
  )
}

export function App() {
  return (
    <HocuspocusProviderWebsocketComponent url="ws://127.0.0.1:1234">
      <HocuspocusRoom name="example-document">
        <Editor />
      </HocuspocusRoom>
    </HocuspocusProviderWebsocketComponent>
  )
}
```

## 切换文档（多路复用）

由于 WebSocket 位于外层组件，切换 `HocuspocusRoom` 上的 `name` 属性会销毁旧的文档提供器，并在不重新连接套接字的情况下创建一个新的：

```tsx
function Workspace({ docId }: { docId: string }) {
  return (
    <HocuspocusProviderWebsocketComponent url="ws://127.0.0.1:1234">
      <HocuspocusRoom name={docId}>
        <Editor />
      </HocuspocusRoom>
    </HocuspocusProviderWebsocketComponent>
  )
}
```

如果你希望在同一个套接字上同时打开多个文档，请将多个 `HocuspocusRoom` 作为同级元素渲染。当连接到 v4 服务器并对许多可能存在文档名冲突的提供器进行多路复用时，可以考虑在底层的 `HocuspocusProviderWebsocket` 上启用 [`sessionAwareness`](https://tiptap.zhcndoc.com/hocuspocus/provider/configuration.md)。
