当前位置: 首页 > article >正文

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

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

🌟 目录

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

🚨 真实报错全记录

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

[Vue warn]: Property "canRedo" was accessed during render but is not defined on instance.

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

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

✅ 修复

return {
  canRedo // 显式暴露给模板
}

案例2:幽灵报错 👻

[object Error] { message: "" }

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

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

✅ 修复

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

案例3:重做按钮罢工 🚫

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

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

✅ 修复

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

🏗️ 极简架构设计

系统流程图

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

模块职责

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

🧩 核心模块实现

历史管理器(精简版)

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()
  }
}

状态同步器

// 🚨 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:历史记录混乱

诊断步骤

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

场景3:移动端失效

解决方案

// 添加触摸事件监听
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 喜欢的点个赞哇
遇到新问题?随时截图发问,我会帮你分析! 💬


http://www.kler.cn/a/544207.html

相关文章:

  • Google 的开源高性能 JavaScript 和 WebAssembly 引擎:V8简单入门,并通过mini-racer进行实践
  • win10 llamafactory模型微调相关②
  • 每日十题八股-补充材料-2025年2月12日
  • 网络工程师 (30)以太网技术
  • 前后端交互方式
  • C++ 模板
  • 【逆向工程】破解unity的安卓apk包
  • React 性能优化的核心方案
  • 2025-2-11算法打卡
  • Mybatis源码02 - 初始化基本过程(引导层部分)
  • Linux进阶——DNS域名解析服务器
  • 计算机网络初识
  • sql难点
  • 微服务SpringCloud Alibaba组件nacos教程【详解naocs基础使用、服务中心配置、集群配置,附有案例+示例代码】
  • 网络分析仪如何看驻波
  • Datawhale Ollama教程笔记2
  • JVM的类加载器
  • Unity中Timeline和Cinemachine制作2D动画:镜头篇
  • 幂等性解决方案(消息队列例子)
  • Python爬虫:高效获取1688商品详情的实战指南
  • vue3: const一个function怎么写呢?
  • Mysql优化的查询语句(1)
  • 路由过滤方法与常用工具
  • DeepSeek是如何通过“蒸馏”技术打造自己的AI模型
  • React中PureComponent的用法
  • 图书管理项目(spring boot + Vue)