---
title: "使用 React 的节点视图"
description: "使用 React 在 Tiptap 中构建自定义节点视图。直接操作节点属性和交互内容。"
canonical_url: "https://tiptap.zhcndoc.com/editor/extensions/custom-extensions/node-views/react"
---

# 使用 React 的节点视图

使用 React 在 Tiptap 中构建自定义节点视图。直接操作节点属性和交互内容。

如果您习惯于使用 React 工作，使用普通 JavaScript 可能会感觉复杂。好消息是：您也可以在节点视图中使用常规的 React 组件。您只需知道一点小知识，让我们逐步来看。

## 渲染 React 组件

要在编辑器中渲染 React 组件，您需要执行以下操作：

1. [创建一个节点扩展](https://tiptap.zhcndoc.com/editor/extensions/custom-extensions.md)
2. 创建一个 React 组件
3. 将该组件传递给提供的 `ReactNodeViewRenderer`
4. 使用 `addNodeView()` 注册
5. [配置 Tiptap 使用您的新节点扩展](https://tiptap.zhcndoc.com/editor/getting-started/configure.md)

您的节点扩展可能看起来像这样：

```js
import { Node } from '@tiptap/core'
import { ReactNodeViewRenderer } from '@tiptap/react'
import Component from './Component.jsx'

export default Node.create({
  // 配置 …

  addNodeView() {
    return ReactNodeViewRenderer(Component)
  },
})
```

要使其正常工作需要一点魔法。但不要担心，我们提供了一个可以帮助您轻松入门的包装组件。不要忘记将其添加到您的自定义 React 组件中，如下所示：

```jsx
<NodeViewWrapper className="react-component">React 组件</NodeViewWrapper>
```

明白了吗？让我们看看实际效果。请随意复制以下示例开始吧。

> **Interactive demo:** [ReactComponent](https://embed.tiptap.dev/preview/GuideNodeViews/ReactComponent?inline=false\&hideSource=false)

不过，该组件并不会与编辑器交互。是时候将其连接起来了。

## 访问节点属性

您在节点扩展中使用的 `ReactNodeViewRenderer`，向您的自定义 React 组件传递了一些非常有用的 props。其中之一是 `node` prop。假设您已向节点扩展添加了名为 `count` 的属性（就像我们在上面的示例中做的那样），您可以这样访问它：

```js
props.node.attrs.count
```

## 更新节点属性

您甚至可以通过节点更新节点属性，借助传递给您的组件的 `updateAttributes` prop。将更新后的属性对象传递给 `updateAttributes` prop：

```js
export default (props) => {
  const increase = () => {
    props.updateAttributes({
      count: props.node.attrs.count + 1,
    })
  }

  // …
}
```

是的，这一切都是响应式的。这是非常无缝的通信，不是吗？

## 添加可编辑内容

还有一个名为 `NodeViewContent` 的组件，可以帮助您向节点视图添加可编辑内容。以下是一个示例：

```jsx
import React from 'react'
import { NodeViewWrapper, NodeViewContent } from '@tiptap/react'

export default () => {
  return (
    <NodeViewWrapper className="react-component">
      <span className="label" contentEditable={false}>
        React 组件
      </span>

      <NodeViewContent className="content" />
    </NodeViewWrapper>
  )
}
```

您不需要添加这些 `className` 属性，可以随意删除它们或传递其他类名。在以下示例中试试：

> **Interactive demo:** [ReactComponentContent](https://embed.tiptap.dev/preview/GuideNodeViews/ReactComponentContent?inline=false\&hideSource=false)

请记住，这些内容是由 Tiptap 渲染的。这意味着您需要告诉允许什么类型的内容，例如在您的节点扩展中使用 `content: 'inline*'`（这是我们在上面的示例中使用的）。

`NodeViewWrapper` 和 `NodeViewContent` 组件渲染一个 `<div>` HTML 标签（对于行内节点则是 `<span>`），但您可以更改它。例如 `<NodeViewContent as="p">` 应该渲染一个段落。不过有一个限制：该标签在运行时必须不可更改。

## 更改节点视图的默认内容标签

默认情况下，`ReactNodeViewRenderer` 渲染的节点视图将始终包含一个包裹的 `div`。如果您想更改此节点的类型，可以将 `contentDOMElementTag` 添加到 `ReactNodeViewRenderer` 选项：

```js
// 这将把 div 转变为 header 标签
return ReactNodeViewRenderer(Component, { contentDOMElementTag: 'header' })
```

## 更改包裹的 DOM 元素

要更改包裹 DOM 元素的标签，您可以在 `ReactNodeViewRenderer` 函数上使用 `as` 选项来更改默认标签名称。

```js
import { Node } from '@tiptap/core'
import { ReactNodeViewRenderer } from '@tiptap/react'
import Component from './Component.jsx'

export default Node.create({
  // 配置 …

  addNodeView() {
    return ReactNodeViewRenderer(Component, { as: 'main' })
  },
})
```

## 在文本选择时更新 `selected`

默认情况下，只有当节点本身通过 `NodeSelection` 被选中时，`selected` prop 才会变为 `true`。这对原子节点很有效，但当光标仅仅放在具有文本内容的节点内部时，并不会触发。如果您希望 `selected` 也反映这种情况，请在 `ReactNodeViewRenderer` 上启用 `selectedOnTextSelection` 选项：

```js
return ReactNodeViewRenderer(Component, { selectedOnTextSelection: true })
```

启用此选项后，只要 `TextSelection` 完全位于节点范围内，`selected` 也会为 `true`——例如，当光标放在节点内容内部的某个位置时。仅与节点部分重叠的选区不被视为已选中。

## 跟踪节点位置

当装饰或位置发生变化但内容未变化时，节点视图不会重新渲染。ProseMirror 会在 `contentDOM` 上原生处理装饰，而 `getPos()` 闭包在调用时始终返回当前位置——即使没有重新渲染。

如果您的组件需要 `getPos()` 在渲染输出中保持响应式（例如显示 `pos: 42` 的拖动手柄），请启用 `trackNodeViewPosition`：

```js
return ReactNodeViewRenderer(Component, { trackNodeViewPosition: true })
```

启用后，组件会在每次位置变化时重新渲染。请谨慎使用——频繁重新渲染可能会影响性能。

## 所有可用的 props

以下是您可以期待的 props 完整列表：

| Prop               | Description                                                                                                                    |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| editor             | 编辑器实例                                                                                                                          |
| node               | 当前节点                                                                                                                           |
| decorations        | 一个装饰数组                                                                                                                         |
| selected           | 当当前节点视图中存在 `NodeSelection` 时为 `true`。当 [`selectedOnTextSelection`](#在文本选择时更新-selected) 启用时，`TextSelection` 完全位于节点内部时也为 `true`。 |
| extension          | 访问节点扩展，例如用于获取选项                                                                                                                |
| getPos()           | 获取当前节点在文档中的位置                                                                                                                  |
| updateAttributes() | 更新当前节点的属性                                                                                                                      |
| deleteNode()       | 删除当前节点                                                                                                                         |

## 拖动

要使您的节点视图可拖动，请在扩展中设置 `draggable: true`，并将 `data-drag-handle` 添加到应该作为拖动句柄的 DOM 元素。
