【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 = maxthis.lock = false // 防重入锁}// 🚨 关键方法:安全推送push(state) {if (this.lock) returnthis.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 - 1setTimeout(() => 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.selectionStarteditor.selection.end = textarea.value.selectionEnd// 写入DOMnextTick(() => {textarea.value.selectionStart = editor.selection.starttextarea.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.mzph.cn/web/69766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

QML 快捷键与Shortcut的使用

一、效果展示 二、源码分享 import QtQuick import QtQuick.Controls import Qt.labs.qmlmodels import QtQuick.Controls.Basic import QtQuick.Layouts import QtQuick.Effects import Qt.labs.platformApplicationWindow {id:rootwidth: 1000height: 730visible: truetitle…

RocketMQ和Kafka如何实现顺序写入和顺序消费?

0 前言 先说明kafka,顺序写入和消费是Kafka的重要特性,但需要正确的配置和使用方式才能保证。本文需要解释清楚Kafka如何通过分区来实现顺序性,以及生产者和消费者应该如何配合。   首先,顺序写入。Kafka的消息是按分区追加写入…

【南方Cass】快捷键0002:合并多段线

快捷键:JOIN 按下快捷键JOIN,然后选择需要合并的对象(多段线),按下回车即可完成合并。

HTML之JavaScript变量和数据类型

HTML之JavaScript变量和数据类型 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</titl…

Qt的isVisible ()函数介绍和判断窗口是否在当前界面显示

1、现象&#xff1a;当Qt的窗口最小化时&#xff0c;isVisible值一定是true&#xff0c;这是正常的。 解释&#xff1a;在Qt中&#xff0c;当你点击窗口的最小化按钮时&#xff0c;Qt内部不会自动调用 hide() 方或 setVisible(false) 来隐藏窗口。相反&#xff0c;它会改变窗口…

【愚公系列】《Python网络爬虫从入门到精通》007-请求模块requests高级应用(Reguests-HTML)

标题详情作者简介愚公搬代码头衔华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。近期荣誉2022年度…

【虚幻引擎UE】UE4.23到UE5.5的核心功能变化

简单总结从UE4.23到UE5.5&#xff0c;虚幻引擎的重大变化&#xff1a; 1. WebGL/HTML5 平台支持和像素流 UE4.23-UE4.25&#xff1a;移除官方HTML5支持&#xff0c;改为社区插件维护。 但通过第三方插件&#xff08;如WebAssemblyWebGPU&#xff09;可在浏览器运行部分项目。U…

win10 离线安装openssh.server

在 Windows 10 上离线安装 OpenSSH Server 可以通过手动安装的方式来达成&#xff0c;因为 OpenSSH 默认并不包含在 Windows 10 的可选功能中。以下是一些步骤来帮助你手动安装 OpenSSH Server&#xff1a; 方法一&#xff1a;使用 PowerShell 安装 启用管理员权限的 PowerShe…

在Vue中,JavaScript数组常用方法,添加,插入,查找,删除等整理

在Vue中&#xff0c;JavaScript数组常用&#xff0c;添加&#xff0c;插入&#xff0c;查找&#xff0c;删除等整理 1.splice()方法可以直接修改原数组&#xff0c;通过指定要删除元素的索引来删除它。 例&#xff1a; let index // 要删除的元素的索引; this.array.splice(i…

【AI论文】CodeI/O: 通过代码输入输出预测来提炼推理模式

摘要&#xff1a;推理是大型语言模型的一项基本能力。尽管先前的研究主要集中在提升如数学或代码生成等狭窄领域的技能&#xff0c;但由于训练数据稀疏且分散&#xff0c;在许多其他推理任务上提高性能仍然具有挑战性。为了解决这个问题&#xff0c;我们提出了CodeI/O&#xff…

AI编程01-生成前/后端接口对表-豆包(或Deepseek+WPS的AI

前言: 做过全栈的工程师知道,如果一个APP的项目分别是前端/后端两个团队开发的话,那么原型设计之后,通过接口文档进行开发对接是非常必要的。 传统的方法是,大家一起定义一个接口文档,然后,前端和后端的工程师进行为何,现在AI的时代,是不是通过AI能协助呢,显然可以…

热更图片方案

项目平常需要对线上一些图片资源修正&#xff0c;所以需要热更图片功能。 远端入口新增字段配json文件 {"1.1.22030303":{"sprite":{"assets/ui/common/images/acient_gold.png" : "https://aaaa.png","assets/ui/common/image…

24电子信息类研究生复试面试问题汇总 电子信息类专业知识问题最全!电子信息复试全流程攻略 电子信息考研复试真题汇总

你是不是在为电子信息考研复试焦虑&#xff1f;害怕被老师问到刁钻问题、担心专业面答不上来&#xff1f;别慌&#xff01;作为复试面试92分逆袭上岸的学姐&#xff0c;今天手把手教你拆解电子信息类复试通关密码&#xff01;看完这篇&#xff0c;让你面试现场直接开大&#xf…

PortSwigger——WebSockets vulnerabilities

文章目录 一、WebSockets二、Lab: Manipulating WebSocket messages to exploit vulnerabilities三、Lab: Manipulating the WebSocket handshake to exploit vulnerabilities四、Using cross-site WebSockets to exploit vulnerabilities4.1 跨站WebSocket劫持&#xff08;cro…

Dockerfile 详解:构建自定义镜像

Dockerfile 是一种文本文件,包含了一系列指令,用于描述如何构建一个 Docker 镜像。通过 Dockerfile,我们可以将应用程序及其所有依赖打包成镜像,确保应用在不同环境中运行时保持一致性。掌握 Dockerfile 的写法和最佳实践,能够帮助我们高效地构建和管理容器镜像。 本文将…

机器视觉中的3d和2d的区别

在机器视觉中&#xff0c;3D和2D的主要区别体现在数据的维度、处理方式及应用场景上。以下是具体对比&#xff1a; 数据维度 2D视觉 &#xff1a;处理二维图像&#xff0c;仅包含宽度和高度信息&#xff0c;通常以像素矩阵表示。 3D视觉 &#xff1a;处理三维数据&#xff0c;…

日语学习-日语知识点小记-构建基础-JLPT-N4N5阶段(5):動詞ます形 > 動詞ない形

日语学习-日语知识点小记-构建基础-JLPT-N4&N5阶段(5):動詞ます形 > 動詞ない形 1、前言(1)情况说明(2)工程师的信仰2、知识点(1)動詞ます形 > 動詞ない形(2)~ないでください:(3)指带词(指示代词):こ そ あ ど3、单词(1)日语单词(2)日语…

Sonic Layer1

礼记有言&#xff1a;良冶之子&#xff0c;必学为裘&#xff1b;良弓之子&#xff0c;必学为箕&#xff1b; 闲来无趣&#xff0c;看看Sonic 的官方文档吧。道听途殊终归了解的不够全面。 首先&#xff0c;看Sonic 是如何介绍自己的&#xff1a; 哇趣&#xff0c;Sonic 把自己的…

C#数据库操作系列---SqlSugar完结篇

1. 不同寻常的查询 之前介绍了针对单个表的查询&#xff0c;同样也是相对简单的查询模式。虽然开发完全够用&#xff0c;但是难免会遇到一些特殊的情况。而下面这些方法就是为了解决这些意料之外。 1.1 多表查询 SqlSugar提供了一种特殊的多表查询方案&#xff0c;使用IQuer…

Pygame: joystick 模块使用示例

pygame几乎可以识别任意外接游戏操纵设备。 游戏手柄上的每个操作都会形成一个电信号被joystick类对象捕获到&#xff0c; joystick把这个信号归一化到[-1,1]区间&#xff0c;或者离散化为{0,1}。 以下程序创建一个弹出窗口&#xff0c;实时显示joystick捕获到的信号数值&…