【CodePen实战:撤销重做功能全记录】

🛠️ CodePen实战:撤销重做功能全记录

🌟 目录

  1. 🚨 真实报错全记录 - 那些折磨我的Bug
  2. 🏗️ 极简架构设计 - 适合实验项目的结构
  3. 🧩 模块实现细节 - 关键代码解析
  4. 🚑 急救方案 - 快速Debug技巧

🚨 真实报错全记录

案例1:Vue的"温柔警告" 💛

bash 复制代码
[Vue warn]: Property "canRedo" was accessed during render but is not defined on instance.

🕵️ 现象 :重做按钮偶尔消失
🔍 诊断过程

  1. 检查模板中的canRedo拼写 ✅
  2. 发现setup()中漏返回属性:
javascript 复制代码
// 错误代码
setup() {
  const canRedo = ref(false)
  // ...忘记return...
}

✅ 修复

javascript 复制代码
return {
  canRedo // 显式暴露给模板
}

案例2:幽灵报错 👻

bash 复制代码
[object Error] { message: "" }

🕵️ 现象 :控制台只显示空错误对象
🔍 诊断

  1. 添加错误边界:
javascript 复制代码
window.onerror = (msg) => console.log('幽灵捕获:', msg)
  1. 发现异步操作未捕获异常:
javascript 复制代码
// 错误代码
setTimeout(() => { throw new Error('test') }, 0)

✅ 修复

javascript 复制代码
// 所有异步操作包裹try-catch
setTimeout(() => {
  try { /* 操作代码 */ }
  catch(e) { console.error(e) }
}, 0)

案例3:重做按钮罢工 🚫

🕵️ 现象 :点击重做无任何反应
🔍 诊断流程

  1. 打印历史记录栈:
javascript 复制代码
console.log('历史栈:', JSON.parse(JSON.stringify(history.stack)))
  1. 发现索引越界:
bash 复制代码
当前索引: 3 | 栈长度: 3
  1. 定位到redo方法:
javascript 复制代码
// 错误代码
index.value += 1 // 当index=2时变成3,而长度是3

✅ 修复

javascript 复制代码
// 添加边界检查
index.value = Math.min(index.value + 1, stack.length - 1)

🏗️ 极简架构设计

系统流程图

输入/删除 撤销/重做 用户输入 操作类型 防抖300ms记录 立即响应 生成快照 获取历史状态 保存到历史栈 更新编辑器

模块职责

模块 职责 代码示例
输入监听 捕获用户操作并防抖 watch(text, debounceFn)
历史管理 存储/检索编辑器状态 history.push(snapshot)
状态同步 保持DOM与数据一致 nextTick(updateSelection)
UI控制 按钮状态/快捷键处理 :disabled="!canUndo"

🧩 核心模块实现

历史管理器(精简版)

javascript 复制代码
class History {
  constructor(max = 20) {
    this.stack = []
    this.index = -1 // 当前状态索引
    this.max = max
    this.lock = false // 防重入锁
  }

  // 🚨 关键方法:安全推送
  push(state) {
    if (this.lock) return
    this.lock = true
    
    // 裁剪后续记录
    this.stack.splice(this.index + 1)
    
    // 容量控制
    if (this.stack.length >= this.max) {
      this.stack.shift()
      this.index = Math.max(this.index - 1, -1)
    }
    
    this.stack.push(JSON.parse(JSON.stringify(state)))
    this.index = this.stack.length - 1
    
    setTimeout(() => this.lock = false, 50)
  }

  // 🚨 关键方法:安全撤销
  undo() {
    this.index = Math.max(this.index - 1, 0)
    return this.get()
  }

  // 🚨 关键方法:安全重做
  redo() {
    this.index = Math.min(this.index + 1, this.stack.length - 1)
    return this.get()
  }
}

状态同步器

javascript 复制代码
// 🚨 DOM与数据同步
const syncSelection = () => {
  // 从DOM读取
  editor.selection.start = textarea.value.selectionStart
  editor.selection.end = textarea.value.selectionEnd
  
  // 写入DOM
  nextTick(() => {
    textarea.value.selectionStart = editor.selection.start
    textarea.value.selectionEnd = editor.selection.end
  })
}

🚑 急救Debug指南

场景1:操作后光标错位

快速检查

  1. 是否在nextTick中更新选区?
  2. 快照是否包含selection数据?
  3. 是否存在CSS影响光标位置?

场景2:历史记录混乱

诊断步骤

javascript 复制代码
// 在push方法中添加日志
console.log('推送快照:', 
  `内容长度: ${state.content.length}`, 
  `光标: ${state.selection.start}-${state.selection.end}`
)

场景3:移动端失效

解决方案

javascript 复制代码
// 添加触摸事件监听
textarea.addEventListener('touchend', saveSelection)

📌 经验总结

  1. Vue响应式陷阱:直接存储响应式对象到历史栈会导致内存泄漏
  2. DOM时序问题:光标操作必须包裹在nextTick中
  3. 防抖重要性:300ms间隔能平衡性能和体验
  4. 边界检查:所有索引操作都要有Math.min/max保护

最后建议 :在CodePen中开发时,每实现一个功能就添加console.log检查点,比调试器更高效! 🐛🔍

完整可运行代码:https://codepen.io/RichardRourc/pen/bNGGJRV?editors=1111

github 仓库完整代码:https://github.com/RichardRourc/undo-redo/blob/main/undo%26redo.html 喜欢的点个赞哇

遇到新问题?随时截图发问,我会帮你分析! 💬

相关推荐
無铭之辈1 小时前
学习Vue的必要基础
前端·vue.js·学习
吉吉安1 小时前
CSS实现中心放大动画
前端·css·动画·css动画·css中心放大动画
汤不离包2 小时前
JavaScript 对象属性遍历与描述详解
前端
111Hertz2 小时前
Vue 3 + Vite:现代前端开发的完美组合
vue.js
九幽归墟2 小时前
浅析Transfer-Encoding: chunked在流式与边缘渲染中的应用
前端·javascript
虾球xz2 小时前
游戏引擎学习第99天
javascript·学习·游戏引擎
A-sleep2 小时前
el-table 对动态列表 中的某一列 指定宽度 内容解密 去除标签
javascript·vue.js·elementui
成长ing121382 小时前
cocos creator 放大镜效果
前端·cocos creator
customer082 小时前
【开源免费】基于SpringBoot+Vue.JS社区养老服务平台(JAVA毕业设计)
java·vue.js·spring boot·spring cloud·开源
noravinsc2 小时前
vue2 definecomponent is not defined
前端·javascript·vue.js