【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 喜欢的点个赞哇

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

相关推荐
Dream-Y.ocean4 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Csvn5 小时前
性能监控
前端
Highcharts.js5 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn6 小时前
构建工具
前端
FRing036 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷7 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen8 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒9 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫9 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
jerrywus9 小时前
我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上
前端·agent·claude