# 扩展模型与组合

理解 create、configure、extend、安装顺序和完整生命周期。

## 先选择功能归属

`Extension` 提供通用行为和预设；`Node` 描述源码节点；`Mark` 描述行内标记。三者共用声明式钩子，不会创建 ProseMirror schema 或另一份 JSON 文档。

| 需求 | 推荐入口 | 完整教程 |
| --- | --- | --- |
| 修改已有功能默认值 | `.configure()` | [容器与嵌套块](/extensions/containers) |
| 保留语法，换按钮和组件 | `.extend({ addNodeView() {} })` | [代码块定制](/extensions/code-blocks) |
| 新语法和可写字段 | `Node.create()` + `defineSource()` | [自定义节点](/extensions/custom-node) |
| 新增 Markdown 解析规则 | `addMarkdownExtensions()` | [Markdown 插件](/extensions/markdown-plugins) |
| 多行图表或结构性预览 | `addProjectionLayers()` | [图表与代码块预览](/extensions/diagrams) |
| 临时状态、快捷键、输入规则 | `Extension.create()` | [状态](/guide/state)、[输入规则](/extensions/input-rules) |

## create：声明可复用定义

~~~ts
import { Extension } from '@quirekit/msfe-core'

interface Options {
  /** Host application name used by this extension. */
  label: string
}
interface Storage {
  /** Accepted source changes observed in this installation. */
  changes: number
}
export const Audit = Extension.create<Options, Storage>({
  name: 'audit',
  addOptions() { return { label: 'editor' } },
  addStorage() { return { changes: 0 } },
  onUpdate() { this.storage.changes += 1 },
})
~~~

定义可在多个编辑器之间复用。`options` 与 `storage` 在各次安装中独立解析，不能把实例状态放在模块级变量中。`storage` 适合临时服务和计数；需要事务映射、撤销或投影依赖的状态使用 `defineState()`。

## configure：修改配置

~~~ts
const ReportAudit = Audit.configure({ label: 'report' })
~~~

返回新定义，原定义保持不变。普通对象递归合并；数组整体替换，函数和非普通对象按值替换。配置容器 `types` 时，要显式保留仍需使用的默认类型。它不是已挂载实例的热更新方法；明暗、模式、只读使用实例对应方法。

## extend：继承和覆盖钩子

~~~ts
const Audited = Audit.extend({
  onUpdate(event) {
    this.parent?.(event)
    console.debug(this.options.label, event.revision)
  },
})
~~~

覆盖钩子不会自动合并父级的结果。需要保留父行为时调用当前钩子的 `this.parent?.()`，数组结果由你显式拼接。需要 `this.options`、`this.storage`、`this.editor`、`this.parent` 的钩子使用普通方法，不能用箭头函数绑定外部 `this`。

`extend` 默认继承名称。用新定义替换预设中对应项，不能把同名原定义和派生定义一起安装。改名也不意味着可以让两个扩展随意占有同一段替换投影。

## 安装一个预设

~~~ts
import { StarterKit } from '@quirekit/msfe-starter-kit'
import { CodeBlock } from '@quirekit/msfe-code-block'

const CustomCodeBlock = CodeBlock.extend({
  // Add the renderer from the code-block tutorial here.
})
const extensions = [
  StarterKit.configure({ codeBlock: CustomCodeBlock, image: false }),
  Audit,
]
~~~

`addExtensions()` 用于组合业务预设；应用只安装该预设一次。`requires` 声明依赖的扩展名称。满足依赖的扩展按 `priority` 从高到低安装，默认优先级为 100。名称冲突、缺失依赖等会在挂载前报告，不应靠调整数组顺序掩盖。

## 生命周期顺序与可做的事

| 阶段 | 已有内容 | 应做的事 |
| --- | --- | --- |
| 配置与状态构建 | 安装定义、配置、状态槽 | 保持可重复、无 DOM 副作用 |
| `onBeforeCreate` | 配置及状态，尚无编辑器 DOM | 准备不依赖挂载的逻辑 |
| `onCreate` | 已挂载实例 | 建立宿主订阅，可返回清理函数 |
| `onUpdate` | 已接受源码变化 | 增量观察、标记未保存 |
| `onTransaction` | 包括选区和效果的已接受事务 | 有界读取，避免每次扫描全文 |
| `onSelectionUpdate` | 选区确实发生变化 | 更新相关工具栏状态 |
| `onDestroy` | 进入释放过程 | 逆序清理，允许重复释放资源的防御 |

组件自己的生命周期比编辑器短。滚动、源码显露、换模式可能导致组件卸载；[异步资源](/extensions/resources) 应绑定对应作用域。全部钩子签名见 [扩展 API](/api/extensions)。

## 从早期描述符迁移

`defineExtension()` 和 `defineNodeView()` 仍是公开的底层入口，但其旧描述符配置不能与现代 `.configure()` 的深合并语义混用。新增功能优先采用这里的作者 API。MSFE 的 Tiptap 风格命名不意味着支持 `parseHTML`、`renderHTML`、`content` schema 或 ProseMirror 插件直接安装。
