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

# 使用 Vue 创建节点视图

使用 Vue 构建自定义节点视图。直接操作节点属性和交互式内容。

如果你习惯使用 Vue，单纯用 Vanilla JavaScript 可能感觉比较复杂。好消息是：你也可以在节点视图中使用常规的 Vue 组件。只需要知道一点点相关知识，下面我们逐步讲解。

## 渲染 Vue 组件

在编辑器内部渲染 Vue 组件的步骤如下：

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

你的节点扩展大致可以这样写：

```js
import { Node } from '@tiptap/core'
import { VueNodeViewRenderer } from '@tiptap/vue-2'
import Component from './Component.vue'

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

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

为了让它正常工作，少许“魔法”操作是需要的。但不用担心，我们提供了一个封装组件，帮助你轻松入门。记得将其添加到你的自定义 Vue 组件中，示范如下：

```html
<template>
  <node-view-wrapper> Vue 组件 </node-view-wrapper>
</template>
```

懂了吗？让我们一起来看看实际效果。以下示例可以直接复制使用。

> **Interactive demo:** [VueComponent](https://embed.tiptap.dev/preview/GuideNodeViews/VueComponent)

不过，这个组件本身不与编辑器交互。现在就来连接它。

## 访问节点属性

你在节点扩展中使用的 `VueNodeViewRenderer` 会向你的自定义 Vue 组件传递一些非常有用的 props。其中之一是 `node` 属性。你可以将以下代码添加到你的 Vue 组件中，以便直接访问节点：

```js
props: {
  node: {
    type: Object,
    required: true,
  },
},
```

这样你就可以在 Vue 组件中访问节点的属性了。假设你在节点扩展中[添加了一个 attribute](https://tiptap.zhcndoc.com/editor/extensions/custom-extensions/extend-existing.md#attributes)，比如 `count`（像刚才的例子中那样），你可以这样访问它：

```js
this.node.attrs.count
```

## 更新节点属性

你甚至可以通过传递给组件的 `updateAttributes` 方法，更新节点的属性。只需在你的组件中添加如下代码：

```js
props: {
  updateAttributes: {
    type: Function,
    required: true,
  },
},
```

然后调用这个函数，并传入一个包含新的属性值的对象：

```js
this.updateAttributes({
  count: this.node.attrs.count + 1,
})
```

是的，这一切都是响应式的，通信非常顺畅，不是挺好的吗？

## 添加内容可编辑区域

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

```html
<template>
  <node-view-wrapper class="dom">
    <node-view-content class="content-dom" />
  </node-view-wrapper>
</template>

<script>
  import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'

  export default {
    components: {
      NodeViewWrapper,
      NodeViewContent,
    },
  }
</script>
```

不一定非要使用这些 `class` 属性，感觉可以删除或替换为其他类名。试试看下面的示例：

> **Interactive demo:** [VueComponentContent](https://embed.tiptap.dev/preview/GuideNodeViews/VueComponentContent)

请记住，这些内容由 Tiptap 渲染。这意味着你需要在节点扩展中声明允许的内容类型，比如通过 `content: 'inline*'`（这是我们上面示例中的设置）。

`NodeViewWrapper` 和 `NodeViewContent` 组件会渲染 `<div>`（内联节点为 `<span>`），但你也可以修改这个行为。例如 `<node-view-content as="p">` 只要是块级标签就可以，默认会渲染为 `<p>`。但有个限制：标签在运行时不能更改。

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

默认情况下，只有当节点本身通过 `NodeSelection` 被选中时，`selected` prop 才会变成 `true`。这对原子节点效果很好，但当光标只是放在一个包含文本内容的节点内部时，它不会触发。如果你希望 `selected` 在这种情况下也能反映出来，请在 `VueNodeViewRenderer` 上启用 `selectedOnTextSelection` 选项：

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

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

## 跟踪节点位置

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

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

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

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

## 所有可用 props

对于高级用例，我们会传递更多 props 给你的组件。

### editor

编辑器实例。

### node

当前节点。

### decorations

装饰的数组。

### selected

当当前节点视图存在 `NodeSelection` 时为 `true`。如果启用了 [`selectedOnTextSelection`](#在文本选择时更新-selected) 选项，并且 `TextSelection` 完全位于节点内部时，也为 `true`。

### extension

访问节点扩展，例如用来获取配置项。

### getPos()

获取当前节点在文档中的位置。

### updateAttributes()

更新当前节点的属性。

### deleteNode()

删除当前节点。

以下是全部可用 props 的完整列表：

```html
<template>
  <node-view-wrapper />
</template>

<script>
  import { NodeViewWrapper } from '@tiptap/vue-2'

  export default {
    components: {
      NodeViewWrapper,
    },

    props: {
      // 编辑器实例
      editor: {
        type: Object,
      },

      // 当前节点
      node: {
        type: Object,
      },

      // 装饰数组
      decorations: {
        type: Array,
      },

      // 当前节点视图存在 `NodeSelection` 时为 `true`，或者
      // 在启用 `selectedOnTextSelection` 选项且 `TextSelection`
      // 完全位于节点内部时为 `true`
      selected: {
        type: Boolean,
      },

      // 访问节点扩展（例如获取配置）
      extension: {
        type: Object,
      },

      // 获取当前节点在文档中的位置
      getPos: {
        type: Function,
      },

      // 更新当前节点的属性
      updateAttributes: {
        type: Function,
      },

      // 删除当前节点
      deleteNode: {
        type: Function,
      },
    },
  }
</script>
```

如果你希望获取所有 props（支持 TypeScript），可以直接引入全部，例如：

```js
// Vue 3
import { defineComponent } from 'src/content/editor/extensions/custom-extensions/node-views/vue.mdx'
import { nodeViewProps } from '@tiptap/vue-3'
export default defineComponent({
  props: nodeViewProps,
})

// Vue 2
import Vue from 'src/content/editor/extensions/custom-extensions/node-views/vue.mdx'
import { nodeViewProps } from '@tiptap/vue-2'
export default Vue.extend({
  props: nodeViewProps,
})
```

## 拖拽支持

若要让你的节点视图支持拖拽，只需在扩展中设置 `draggable: true`，并将 `data-drag-handle` 添加到作为拖拽手柄的 DOM 元素即可。

> **Interactive demo:** [DragHandle](https://embed.tiptap.dev/preview/GuideNodeViews/DragHandle)
