Pinia可构建轻量富文本历史系统:1.分离内容与历史状态;2.封装track/undo/redo方法精准记录语义操作;3.绑定快捷键与UI控制,支持可选持久化。

Pinia 本身不直接提供富文本历史版本管理能力,但可以结合其响应式机制、插件扩展性和状态快照思想,构建轻量、可控、可回溯的富文本编辑历史系统。关键不在“存全部”,而在“精准记录用户意图操作”——比如一次格式化、一段插入、一个删除,而不是每敲一个字都快照。
明确富文本历史管理的核心需求
- 用户能撤销/重做最近几次编辑操作(非字符级,而是语义级)
- 历史栈不随 UI 状态(如选区高亮、loading)污染
- 支持跨组件访问(如编辑器、工具栏、历史面板)
- 刷新后可选恢复最新有效版本(需配合持久化)
用 Pinia 实现富文本历史版本管理的三步法
1. 设计可快照的 state 结构,分离「内容」与「历史」
把富文本核心数据(如 HTML 字符串、JSON 描述、或 Tiptap 的 JSON Schema)放在独立字段中,避免对整个 store 做深拷贝:
// src/stores/useEditorStore.ts
import { defineStore } from 'pinia'
import { ref, toRaw } from 'vue'
export const useEditorStore = defineStore('editor', () => {
// ✅ 主内容状态(只对这个对象做快照)
const content = ref<string>('<p>欢迎编辑</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/gongju/2234" title="Vuejs"><img
src="https://img.php.cn/upload/manual/001/242/473/6a06adfe771d0724.png" alt="Vuejs" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/gongju/2234" title="Vuejs">Vuejs</a>
<p>Vue.js 官方推荐通过 npm 进行安装,这是构建现代化前端项目最主流的方式。在终端中执行 npm install vue 即可获取最新稳定版,如需指定版本可添加 @版本号,例如 npm install vue@3.4.0。安装前请确保已配置 Node.js 环境(推荐 v18 及以上版本),并建议将 npm 镜像源切换至国内镜像以提升下载速度。对于大型项目,还可通过 npm create v</p>
</div>
<a href="/xiazai/gongju/2234" title="Vuejs" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div>')
// ✅ 历史栈:存储 content 的纯净快照 + 操作描述
const undoStack = ref<Array<{ snapshot: string; desc: string }>>([])
const redoStack = ref<Array<{ snapshot: string; desc: string }>>([])
// ⚠️ 不参与快照的 UI 状态(不影响内容语义)
const isFocused = ref(false)
const selectionRange = ref<Range | null>(null)
return {
content,
undoStack,
redoStack,
isFocused,
selectionRange,
}
})2. 封装 track / undo / redo 方法,保持响应式安全
在 store 内部定义业务动作,调用前 track(),还原时用 Object.assign 或直接赋值(ref 赋值天然响应式):
// 接上例,在 defineStore 返回对象中添加:
const track = (desc = '编辑') => {
// 保存当前 content 快照(toRaw 防止 proxy 干扰序列化)
const rawSnapshot = toRaw(content.value)
undoStack.value.push({ snapshot: rawSnapshot, desc })
redoStack.value = [] // 清空重做栈
}
const undo = () => {
if (undoStack.value.length === 0) return
const last = undoStack.value.pop()!
redoStack.value.push({ snapshot: content.value, desc: `重做:${last.desc}` })
content.value = last.snapshot // ✅ 直接赋值,响应式生效
}
const redo = () => {
if (redoStack.value.length === 0) return
const last = redoStack.value.pop()!
undoStack.value.push({ snapshot: content.value, desc: `撤销:${last.desc}` })
content.value = last.snapshot
}
const insertText = (text: string) => {
track(`插入:“${text.substring(0, 12)}…”`)
content.value = content.value.replace('</p>', `${text}</p>`)
}
const clearAll = () => {
track('清空内容')
content.value = '<p></p>'
}✅ 优势:不依赖第三方 diff 库;快照是字符串,体积小、比对快;操作描述可用于 UI 显示(如“撤销加粗标题”)。
3. 在组件中绑定快捷键与历史 UI 控制
使用 @vueuse/core 监听 Ctrl+Z / Ctrl+Y,并动态控制按钮可用性:
<script setup lang="ts">
import { useEditorStore } from '@/stores/useEditorStore'
import { onKeyStroke, useVModel } from '@vueuse/core'
const store = useEditorStore()
// 快捷键支持(仅限桌面端)
onKeyStroke('z', () => store.undo(), {
prevent: true,
eventName: 'keydown',
target: window
})
onKeyStroke(['y', 'shift+y'], () => store.redo(), {
prevent: true,
eventName: 'keydown',
target: window
})
// 按钮禁用逻辑
const canUndo = computed(() => store.undoStack.length > 0)
const canRedo = computed(() => store.redoStack.length > 0)
</script>
<template>
<div class="toolbar">
<button @click="store.undo" :disabled="!canUndo">撤销</button>
<button @click="store.redo" :disabled="!canRedo">重做</button>
<small>{{ store.undoStack.length }} 步可撤 | {{ store.redoStack.length }} 步可重</small>
</div>
</template>进阶建议:按需持久化历史(可选)
若需刷新后保留最近 5 条历史(非全部),可在 store 初始化时读取、变更时写入 localStorage:
// 初始化时恢复
const savedHistory = localStorage.getItem('editor-history')
if (savedHistory) {
try {
const parsed = JSON.parse(savedHistory)
undoStack.value = parsed.undo || []
redoStack.value = parsed.redo || []
} catch (e) {}
}
// 变更后保存(节流写入,避免高频触发)
watch(
() => [store.undoStack, store.redoStack],
() => {
localStorage.setItem('editor-history', JSON.stringify({
undo: store.undoStack.slice(-5), // 只存最近5条
redo: store.redoStack.slice(-5)
}))
},
{ deep: true }
)不复杂但容易忽略:富文本历史不是“时间机器”,而是“操作日志”。Pinia 提供的是可靠的容器和响应式底座,真正让撤销重做好用的,是你对「什么算一次有效编辑」的判断。

















