# 通用 API

- Human documentation: [https://docs.univer.ai/zh-CN/guides/sheets/features/core/general-api](https://docs.univer.ai/zh-CN/guides/sheets/features/core/general-api)

- Agent Markdown: [https://docs.univer.ai/zh-CN/guides/sheets/features/core/general-api.md](https://docs.univer.ai/zh-CN/guides/sheets/features/core/general-api.md)

- Requested language: `zh-CN`

- Content language: `zh-CN`

- Documentation version: `1.0.0-rc.0`

- Source: [sheets/features/core/general-api.zh-CN.mdx](https://github.com/dream-num/documentation/blob/dev/content/guides/sheets/features/core/general-api.zh-CN.mdx)

---

在 Univer 中，根据文档类型的不同，所能调用的 Facade API 也有所不同。本章节将介绍在所有类型文档都适用的通用 Facade API。

## 引入

```typescript
import { FUniver } from '@univerjs/core/facade'

const univerAPI = FUniver.newAPI(univer)
```

## 概念

### 命令

Univer 中大多数的操作都会注册到命令系统，并通过命令系统来触发。这种统一的操作方式使得 Univer 可以很容易的实现撤销、重做、协同等功能。

> [!NOTE]
> 如需了解更多设计细节请阅读 
> 
> [Univer 命令系统](/blog/univer#command-system)
> 
> 。

## 命令系统

Univer 为用户提供了统一的命令系统，通过命令系统用户可以实现各种定制化的功能。

> [!NOTE]
> 每一个命令对应了一个唯一的 ID。如果你正在寻找某个特定的命令 ID，可以参考 [如何查找命令
> ID](/blog/find-the-command-id)。

### 监听命令

Univer 设计了两种监听命令的方式，分别是在命令执行前和在命令执行后：

* `Event.BeforeCommandExecute`：在命令执行前执行自定义逻辑。
* `Event.CommandExecuted`：在命令执行后执行自定义逻辑。

#### 监听命令执行前

命令执行前，将 `Event.BeforeCommandExecute` 传给 `FUniver.addEvent` 来注册预处理监听器。

当命令执行前，预处理监听器内逻辑会被执行。

```typescript
const univerAPI = FUniver.newAPI(univer)

univerAPI.addEvent(univerAPI.Event.BeforeCommandExecute, ({ id, type, params }) => {
  // 在命令执行前执行自定义逻辑
})
```

如需阻止命令执行，在监听器中将 `event.cancel` 设为 `true`。

```typescript
const univerAPI = FUniver.newAPI(univer)

univerAPI.addEvent(univerAPI.Event.BeforeCommandExecute, (event) => {
  event.cancel = true
})
```

#### 监听命令执行后

命令执行后，将 `Event.CommandExecuted` 传给 `FUniver.addEvent` 来注册后处理监听器。

当命令执行后，后处理监听器内逻辑会被执行。

```typescript
const univerAPI = FUniver.newAPI(univer)

univerAPI.addEvent(univerAPI.Event.CommandExecuted, ({ id, type, params }) => {
  // 在命令执行后执行自定义逻辑
})
```

### 取消监听

注册命令监听器的方法会返回一个 `IDisposable` 对象，调用 `IDisposable.dispose` 可以销毁该监听器。

建议你及时销毁不再使用的监听器，有助于提高程序的健壮性。

```typescript
const univerAPI = FUniver.newAPI(univer)

// 注册监听器
const disposable = univerAPI.addEvent(univerAPI.Event.BeforeCommandExecute, () => {
  // 在命令执行前执行自定义逻辑
})

// 示例：1 秒后取消监听
setTimeout(() => {
  // 取消监听
  disposable.dispose()
}, 1000)
```

### 执行命令

如果你知道命令 ID 和所需要传递的参数，也可以通过 `FUniver.executeCommand` 方法来执行命令。

例如，我们可以通过 `sheet.command.set-range-values` 命令来设置单元格 A1 的值：

```typescript
const univerAPI = FUniver.newAPI(univer)

// 执行命令
univerAPI.executeCommand('sheet.command.set-range-values', {
  value: { v: 'Hello, Univer!' },
  range: { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0 },
})
```

## 事件系统

Univer 提供了一个完整的事件系统，允许你监听和响应电子表格中的各种变化。事件可以分为多个类别，包括剪贴板操作、选区变化、单元格交互、表格修改等。

### 事件类别

完整事件列表请参考：/reference/facade/events#%E5%B1%9E%E6%80%A7

1. **剪贴板事件**
   * `BeforeClipboardChange`: 在剪贴板内容改变前触发，可以通过设置 `params.cancel = true` 来阻止改变
   * `BeforeClipboardPaste`: 在粘贴内容前触发，可以通过设置 `params.cancel = true` 来阻止粘贴
   * `ClipboardChanged`: 在剪贴板内容改变后触发，可以获取新的剪贴板内容
   * `ClipboardPasted`: 在内容粘贴后触发，可以获取粘贴的内容

2. **选区事件**
   * `SelectionChanged`: 在选区改变时触发，提供新的选区范围信息
   * `SelectionMoveStart`: 在选区开始移动时触发，可用于实现自定义选区效果
   * `SelectionMoveEnd`: 在选区结束移动时触发，提供最终的选区范围
   * `SelectionMoving`: 在选区移动过程中触发，可用于实时更新UI
   * `DragOver`: 在拖动经过单元格时触发
   * `Drop`: 在放置选区内容时触发

3. **单元格事件**
   * `CellClicked`: 在单元格被点击时触发，提供单元格的位置和内容信息
   * `CellHover`: 在鼠标悬停在单元格上时触发，可用于显示提示信息
   * `CellPointerDown`: 在单元格上按下指针时触发，用于开始拖动或选择操作
   * `CellPointerUp`: 在单元格上释放指针时触发，用于完成操作
   * `CellPointerMove`: 在指针在单元格上移动时触发

4. **表格事件**
   * `SheetValueChanged`: 在表格值改变时触发，提供变更的范围和新值
   * `SheetZoomChanged`: 在缩放级别改变时触发，可获取新的缩放比例
   * `SheetSkeletonChanged`: 在表格结构改变时触发，如行列的插入删除
   * `BeforeSheetEditStart`: 在单元格开始编辑前触发，可用于控制是否允许编辑
   * `SheetEditStarted`: 在单元格开始编辑时触发，可用于自定义编辑器行为
   * `BeforeSheetEditEnd`: 在单元格结束编辑前触发，可用于验证编辑内容
   * `SheetEditEnded`: 在单元格结束编辑时触发，提供编辑后的值
   * `SheetEditChanging`: 在单元格编辑过程中触发，可用于实时验证或格式化
   * `Scroll`: 在表格滚动时触发，提供滚动位置信息
   * `CrosshairHighlightColorChanged`: 在十字光标高亮颜色改变时触发
   * `CrosshairHighlightEnabledChanged`: 在启用或禁用十字光标高亮时触发

5. **表头事件**
   * `RowHeaderClick`: 在行表头被点击时触发
   * `RowHeaderHover`: 在鼠标悬停在行表头时触发
   * `ColumnHeaderClick`: 在列表头被点击时触发
   * `ColumnHeaderHover`: 在鼠标悬停在列表头时触发

6. **数据验证事件**
   * `BeforeSheetDataValidationAdd`: 在添加数据验证规则前触发
   * `SheetDataValidationChanged`: 在数据验证规则改变时触发
   * `SheetDataValidatorStatusChanged`: 在数据验证状态改变时触发

7. **评论事件**
   * `BeforeCommentAdd`: 在添加评论前触发
   * `CommentAdded`: 在评论添加后触发
   * `BeforeCommentUpdate`: 在更新评论前触发
   * `CommentUpdated`: 在评论更新后触发
   * `BeforeCommentDeleted`: 在删除评论前触发
   * `CommentDeleted`: 在评论删除后触发

8. **数据处理事件**
   * `SheetBeforeRangeSort`: 在范围排序前触发
   * `SheetRangeSorted`: 在范围排序后触发
   * `SheetBeforeRangeFilter`: 在范围筛选前触发
   * `SheetRangeFiltered`: 在范围筛选后触发
   * `BeforePivotTableAdd`: 在添加数据透视表前触发
   * `PivotTableAdded`: 在数据透视表添加后触发

### 使用事件

你可以使用 `univerAPI` 的 `addEvent` 方法来监听事件。以下是基本模式：

```typescript
univerAPI.addEvent(univerAPI.Event.事件名称, (params) => {
  // 处理事件参数
})
```

#### 示例：监听单元格点击

```typescript
univerAPI.addEvent(univerAPI.Event.CellClicked, (params) => {
  const { worksheet, workbook, row, column } = params
  console.log(`单元格被点击，行：${row}，列：${column}`)
})
```

#### 示例：监控剪贴板操作

```typescript
univerAPI.addEvent(univerAPI.Event.BeforeClipboardPaste, (params) => {
  const { text, html } = params
  // 如果要取消粘贴操作
  params.cancel = true
})
```

#### 示例：跟踪选区变化

```typescript
univerAPI.addEvent(univerAPI.Event.SelectionChanged, (params) => {
  const { worksheet, workbook, selections } = params
  console.log('选区已改变:', selections)
})
```

### 取消事件监听

事件监听器会返回一个 `IDisposable` 对象，可以用它来在不再需要时移除监听器：

```typescript
const disposable = univerAPI.addEvent(univerAPI.Event.SheetValueChanged, (params) => {
  // 处理值变化
})

// 之后，当你想要移除监听器时：
disposable.dispose()
```

### 最佳实践

1. 当不再需要时，务必销毁事件监听器以防止内存泄漏
2. 根据具体用例选择合适的事件 - 优先使用特定事件而不是通用事件
3. 保持事件处理程序轻量化以维持性能
4. 当需要阻止默认行为时，考虑使用 `BeforeClipboardChange` 等“前置”事件

## 撤销和重做

### 撤销

```typescript
await univerAPI.undo()
```

### 重做

```typescript
await univerAPI.redo()
```

## 系统剪切板

使用 [`FUniver.copy()`](https://docs.univer.ai/zh-CN/reference/facade/univer.md#copy) 和 [`FUniver.paste()`](https://docs.univer.ai/zh-CN/reference/facade/univer.md#paste) 方法可以读取和写入系统剪贴板。

> [!NOTE]
> 复制与粘贴依赖浏览器原生 API
> ，当[环境条件或者权限](https://developer.mozilla.org/zh-CN/docs/Web/API/Clipboard/writeText)不满足时复制与粘贴功能将无法工作。

示例: Sheet 区域的复制与粘贴

```typescript
// 防止在控制台中执行复制粘贴代码时因失去焦点而失败，
// 此示例监听单元格点击事件并执行复制粘贴代码。
univerAPI.addEvent(univerAPI.Event.CellClicked, async (params) => {
  const fWorkbook = univerAPI.getActiveWorkbook()
  const fWorksheet = fWorkbook.getActiveSheet()

  // 将范围 A1:B2 复制到剪贴板
  const fRange = fWorksheet.getRange('A1:B2')
  fRange.activate().setValues([
    [1, 2],
    [3, 4],
  ])
  await univerAPI.copy()

  // 将复制的内容粘贴到范围 C1:D2
  const fRange2 = fWorksheet.getRange('C1')
  fRange2.activate()
  await univerAPI.paste()

  // 检查粘贴的内容
  console.log(fWorksheet.getRange('C1:D2').getValues()) // [[1, 2], [3, 4]]
})
```

你也可以使用[命令系统](#命令系统)实现调用剪贴板功能：

```typescript
import { CopyCommand, PasteCommand } from '@univerjs/ui'

// 复制
univerAPI.executeCommand(CopyCommand.id)
// 粘贴
univerAPI.executeCommand(PasteCommand.id)
```

## UI

请参考下面文档来拓展 Univer 的 UI :

* [自定义绘制内容](/blog/custom-canvas)

## WebSocket

Facade API 提供了一个便捷的 API `createSocket` 来创建 WebSocket，传入一个 URL 即可。
然后可以监听 open、message、close、error 事件，以及主动发送消息 send 方法和主动关闭 close 方法。

使用 Presets 安装，可以直接使用 `univerAPI.createSocket(url)`。
使用手动组合安装，需要安装 [`@univerjs/network`](https://docs.univer.ai/zh-CN/reference/packages/plugins/univerjs/network.md) 依赖包，并注册 `UniverNetworkPlugin` 插件。

```typescript
// ... 省略其他插件引入
import { UniverNetworkPlugin } from '@univerjs/network'

// ... 省略其他 facade 引入
import '@univerjs/network/facade'

// ... 省略其他插件注册
univer.registerPlugin(UniverNetworkPlugin)
```

以下是一个简单的示例：

```typescript
// URL 换成你自己 WebSocket 服务的地址
const ws = univerAPI.createSocket('ws://47.100.177.253:8449/ws')

ws.open$.subscribe(() => {
  console.log('websocket opened')
  ws.send('hello')
})

ws.message$.subscribe((message) => {
  console.log('websocket message', message)
  const content = JSON.parse(message.data).content
  if (!content.includes('command')) {
    return
  }

  const commandInfo = JSON.parse(content)
  const { command, options } = commandInfo
  const { id, params } = command

  // 接受到协同数据，本地落盘
  univerAPI.executeCommand(id, params, options)
})

ws.close$.subscribe(() => {
  console.log('websocket closed')
})

ws.error$.subscribe((error) => {
  console.log('websocket error', error)
})

univerAPI.addEvent(univerAPI.Event.CommandExecuted, ({ id, type, params, options }) => {
  // 仅同步本地 mutation
  if (
    type !== univerAPI.Enum.CommandType.MUTATION ||
    options?.fromCollab ||
    options?.onlyLocal ||
    id === 'doc.mutation.rich-text-editing'
  ) {
    return
  }

  const commandInfo = JSON.stringify({ command: { id, type, params }, options: { fromCollab: true } })
  ws.send(commandInfo)
})
```

注意：启动 Univer 的时候要确保有 unitID ，不指定 unitID 的话无法协同。

## 注册公式

使用 Facade API，可以方便快速的在当前 Univer 实例中注册自定义公式。

如下案例所示，使用 [`registerFunction`](https://docs.univer.ai/zh-CN/reference/facade/formula.md#registerfunction) 将一个 `CUSTOMSUM` 公式所需要的算法、名称、描述一次性注册到公式插件，执行之后就可以使用公式了。在任一空白单元格输入 `=CUSTOMSUM` 可以看到提示。

```typescript
// 注册公式支持 lambda 函数
const formulaEngine = univerAPI.getFormula()
formulaEngine.registerFunction(
  'CUSTOMSUM',
  (...variants) => {
    let sum = 0
    const last = variants[variants.length - 1]

    if (last.isLambda && last.isLambda()) {
      variants.pop()
      const variantsList = variants.map((variant) => (Array.isArray(variant) ? variant[0][0] : variant))
      sum += last.executeCustom(...variantsList).getValue()
    }

    for (const variant of variants) {
      sum += Number(variant) || 0
    }

    return sum
  },
  '求参数的和',
)

// Use the function in a cell
const fWorkbook = univerAPI.getActiveWorkbook()
const fWorksheet = fWorkbook.getActiveSheet()
const cellA1 = fWorksheet.getRange('A1')
cellA1.setValue(1)
const cellA2 = fWorksheet.getRange('A2')
cellA2.setValue(2)
const cellA3 = fWorksheet.getRange('A3')
cellA3.setValue({ f: '=CUSTOMSUM(A1,A2,LAMBDA(x,y,x*y))' })

// A3 will display: 5
formulaEngine.calculationEnd((functionsExecutedState) => {
  if (functionsExecutedState === 3) {
    console.log(cellA3.getValue()) // 5
  }
})
```

如果需要卸载所注册的公式，可以调用 `dispose` 方法。

```typescript
const functionDisposable =
  formulaEngine.registerFunction(
    // calculate
  )

// 卸载所注册的公式
functionDisposable.dispose()
```

如果想要提供更完善的国际化内容和描述，还可以配置 `locales` 和 `description` 字段。如下所示。

```typescript
formulaEngine.registerFunction(
  'CUSTOMSUM',
  (...variants) => {
    let sum = 0
    const last = variants[variants.length - 1]

    if (last.isLambda && last.isLambda()) {
      variants.pop()
      const variantsList = variants.map((variant) => (Array.isArray(variant) ? variant[0][0] : variant))
      sum += last.executeCustom(...variantsList).getValue()
    }

    for (const variant of variants) {
      sum += Number(variant) || 0
    }

    return sum
  },
  {
    description: {
      functionName: 'CUSTOMSUM',
      description: 'formulaCustom.CUSTOMSUM.description',
      abstract: 'formulaCustom.CUSTOMSUM.abstract',
      functionParameter: [
        {
          name: 'formulaCustom.CUSTOMSUM.functionParameter.number1.name',
          detail: 'formulaCustom.CUSTOMSUM.functionParameter.number1.detail',
          example: 'A1:A20',
          require: 1,
          repeat: 0,
        },
        {
          name: 'formulaCustom.CUSTOMSUM.functionParameter.number2.name',
          detail: 'formulaCustom.CUSTOMSUM.functionParameter.number2.detail',
          example: 'B2:B10',
          require: 0,
          repeat: 1,
        },
      ],
    },
    locales: {
      zhCN: {
        formulaCustom: {
          CUSTOMSUM: {
            description: '将单个值、单元格引用或是区域相加，或者将三者的组合相加。',
            abstract: '求参数的和',
            functionParameter: {
              number1: {
                name: '数值1',
                detail:
                  '要相加的第一个数字。 该数字可以是 4 之类的数字，B6 之类的单元格引用或 B2:B8 之类的单元格范围。',
              },
              number2: {
                name: '数值2',
                detail: '这是要相加的第二个数字。 可以按照这种方式最多指定 255 个数字。',
              },
            },
          },
        },
      },
      enUS: {
        formulaCustom: {
          CUSTOMSUM: {
            description: `You can add individual values, cell references or ranges or a mix of all three.`,
            abstract: `Adds its arguments`,
            functionParameter: {
              number1: {
                name: 'number1',
                detail:
                  'The first number you want to add. The number can be like 4, a cell reference like B6, or a cell range like B2:B8.',
              },
              number2: {
                name: 'number2',
                detail: 'This is the second number you want to add. You can specify up to 255 numbers in this way.',
              },
            },
          },
        },
      },
    },
  },
)
```

说明

* `func` 编写计算公式的具体算法和名称映射。入参为使用公式时用户输入的内容，可能为数字、字符串、布尔值，或者一个数组。
* `description` 设置自定义公式的描述。
* `locales` 下可以设置多种语言，命名规则参考 [LocaleType](https://reference.univer.ai/zh-CN/globals#localetype)。可以在 `functionList` 下添加多个公式的翻译。详细的字段说明请参考 [如何在 Formula Engine 中添加公式](/blog/custom-formula#how-to-add-formulas-in-formula-engine) 的部分。

> [!NOTE]
> 如果想复用公式系统提供的算法，增强公式算法的能力，可以通过插件配置的方式注册自定义公式，详细教程请参考
> [自定义公式](/blog/custom-formula/)。

## 枚举类型 API

Facade API 提供了一些常用的枚举类型，可以在开发过程中使用。例如：

```typescript
console.log(univerAPI.Enum)
console.log(univerAPI.Enum.UniverInstanceType.UNIVER_SHEET)
console.log(univerAPI.Enum.LifecycleStages.Rendered)
```

## 工具方法 API

Facade API 提供了一些常用的工具方法，可以在开发过程中使用。例如：

```typescript
console.log(univerAPI.Util)
console.log(univerAPI.Util.tools.chatAtABC(10))
console.log(univerAPI.Util.tools.ABCatNum('K'))
```
