# 挂载与销毁

让应用拥有容器和存储，让编辑器拥有输入、选区与运行时资源。

## 原生 DOM

`createMsfeEditor()` 可以直接用于普通网页。下面的 `mount.ts` 不依赖 Vue；Vue 文件只负责文档站的演示容器。

### native/editor.vue

```vue
<script setup lang="ts">
import { onMounted, onBeforeUnmount, shallowRef } from "vue";
import type { MsfeEditor } from "@quirekit/msfe-core";
import { mountEditor } from "./mount";
import type { ExampleLocale } from "../shared/types";
const props = withDefaults(defineProps<{ /** Host language for initial content and accessible naming. */ locale?: ExampleLocale }>(), { locale: "zh-CN" });
const parent = shallowRef<HTMLElement | null>(null);
let editor: MsfeEditor | undefined;
// Vue is only this documentation page's host; mount.ts has no framework dependency.
onMounted(() => { if (parent.value) editor = mountEditor(parent.value, props.locale); });
onBeforeUnmount(() => editor?.destroy());
</script>
<template><div class="p-qk-surface"><div ref="parent" class="overflow-hidden rounded-qk-control border border-qk-border-subtle" /></div></template>

```

### native/mount.ts

```ts
import { createMsfeEditor, type MsfeEditor } from "@quirekit/msfe-core";
import { StarterKit } from "@quirekit/msfe-starter-kit";
import { Appearance } from "../shared/appearance";
import { exampleMessages } from "../shared/messages";
import type { ExampleLocale } from "../shared/types";

/** @param parent - Host-owned element. @param locale - Host language. @returns Editor that the host must destroy on unmount. */
export function mountEditor(parent: HTMLElement, locale: ExampleLocale = "zh-CN"): MsfeEditor {
  const text = exampleMessages[locale];
  return createMsfeEditor({ parent, markdown: text.nativeInitial, mode: "live",
    label: text.nativeEditor, extensions: [StarterKit, Appearance] });
}

```

### shared/appearance.ts

```ts
import { Extension } from "@quirekit/msfe-core";
import { EditorView } from "@quirekit/cm-view";
import { Prec } from "@quirekit/cm-state";
import { HighlightStyle, syntaxHighlighting } from "@quirekit/cm-language";
import { tags } from "@lezer/highlight";
import { themeVariableReferences as token } from "@quirekit/ui";

/** Consumer-owned theme; source semantics stay in the installed feature packages. */
export const Appearance = Extension.create({ name: "documentationAppearance",
  /** @returns Scoped typography, surfaces and syntax colors from the host design system. */
  addCodeMirrorExtensions() {
    return Prec.high([
      EditorView.theme({
        "&": { ...token.typography.body, color: token.color.text.primary, backgroundColor: token.color.surface.page, minHeight: "180px" },
        ".cm-scroller": { fontFamily: "inherit", lineHeight: "1.8" },
        ".cm-content": { padding: token.spacing.surface, caretColor: token.color.text.primary },
        ".cm-line": { padding: "0" },
        ".cm-gutters": { color: token.color.text.tertiary, backgroundColor: token.color.surface.subtle, border: "none" },
        ".cm-activeLine, .cm-activeLineGutter": { backgroundColor: "transparent" },
        ".cm-selectionBackground, &.cm-focused .cm-selectionBackground": { backgroundColor: token.color.selection.document },
        ".cm-cursor": { borderLeftColor: token.color.text.primary },
        ".msfe-h1": { ...token.typography.pageTitle },
        ".msfe-h2, .msfe-h3": { ...token.typography.sectionTitle },
        ".msfe-heading": { paddingBlock: token.spacing.inline },
        ".msfe-link": { color: token.color.text.accent },
        ".msfe-code-line, .msfe-inline-code": { ...token.typography.code },
        ".consumer-status": { verticalAlign: "middle" },
        "&.cm-focused": { outline: "none" },
      }),
      syntaxHighlighting(HighlightStyle.define([
        { tag: tags.heading, color: token.color.text.primary, fontWeight: "600" },
        { tag: tags.strong, fontWeight: "700" },
        { tag: tags.emphasis, fontStyle: "italic" },
        { tag: [tags.link, tags.url], color: token.color.text.accent },
        { tag: tags.processingInstruction, color: token.color.text.tertiary },
        { tag: tags.monospace, color: token.color.text.secondary },
      ])),
    ]);
  },
});

```

### shared/messages.ts

```ts
const chinese = {
  editor: "Markdown 示例编辑器", modes: "编辑器模式", source: "源码", live: "实时预览", locked: "只读",
  undo: "撤销", redo: "重做", reset: "重置", readMarkdown: "读取 Markdown", readingPreview: "阅读预览", insertStatus: "插入状态", done: "已完成", todo: "待处理",
  nativeEditor: "原生 DOM 示例编辑器", statusEditor: "自定义状态编辑器",
  initial: "## 把想法写下来\n\n在这里编辑 **Markdown**，也可以切换到源码。\n\n- [x] 保留 Markdown 原文\n- [ ] 试试修改这一行\n",
  nativeInitial: "## 原生 DOM 接入\n\n这个实例直接使用 `createMsfeEditor()`。\n",
  statusInitial: "## 自定义状态\n\n设计评审：[[todo]]\n\n点击状态按钮，然后读取 Markdown 或撤销。\n",
};
const english: { [Key in keyof typeof chinese]: typeof chinese[Key] } = {
  editor: "Markdown example editor", modes: "Editor mode", source: "Source", live: "Live", locked: "Locked",
  undo: "Undo", redo: "Redo", reset: "Reset", readMarkdown: "Read Markdown", readingPreview: "Reading preview", insertStatus: "Insert status", done: "Done", todo: "Pending",
  nativeEditor: "Native DOM example editor", statusEditor: "Custom status editor",
  initial: "## Write your ideas\n\nEdit **Markdown** here, or switch to source.\n\n- [x] Preserve the Markdown source\n- [ ] Try editing this line\n",
  nativeInitial: "## Native DOM integration\n\nThis instance uses `createMsfeEditor()` directly.\n",
  statusInitial: "## Custom status\n\nDesign review: [[todo]]\n\nClick the status, then read the Markdown or undo.\n",
};
export const exampleMessages = { "zh-CN": chinese, en: english };

```

### shared/types.ts

```ts
/** Example host language; independent of the documentation router. */
export type ExampleLocale = "zh-CN" | "en";

```

## 生命周期边界

在 DOM 容器就绪后创建，在容器被移除前调用 `destroy()`。销毁会释放编辑器插件、监听器和视图资源。不要在框架的每次 render 中重新创建实例。

一个实例对应一份独立内容、选区和历史。多个实例可以共享扩展定义，但各自的可变状态由实例持有。

## 切换文档

`replaceDocument(markdown, documentId)` 显式换文档并重置历史与选区。需要合并同一份文档的外部修改时，使用带版本检查的 `applyChanges()`，详见[读取与保存](/guide/saving)。

## 异步任务

自定义节点的 `load(attrs, signal)` 可以返回资源及 `release()`。字段变化、卸载或销毁后，响应应遵守取消信号；过期结果不会成为当前节点的资源。持久业务请求由宿主应用管理。

## DOM 与宿主责任

运行时管理 parent 内自己的编辑器节点；应用不要在每次框架更新时覆盖该区域的 innerHTML。多个编辑器使用不同 parent，并给辅助技术提供不同的 label。只共享不可变定义，不共享业务可变实例。

如果不用适配器而自行调用 `createComponentHost()`，宿主也要管理该 host 的销毁。先结束编辑器及挂载组件，再释放组件宿主，避免孤立 Portal / Teleport。

## 页面切换的完整顺序

保存或保留未保存内容 → 取消文档级业务请求 → 销毁实例 → 移除宿主容器。重新打开页面时，用保存的 Markdown 创建新实例。需要保留同一份文档历史的 UI 模式切换应调用 setMode，而不是销毁重建。

资源应归属 editor.scope、documentScope 或组件 scope 中最合适的一层；示例和取消策略见 [异步资源](/extensions/resources)。
