亚洲有码Av一区二区三区_国产高清啪啪免费视频_69色视频国产_国产成人人人爆出白浆_国产精品自在线拍国_一本久久伊人热热精品无码_午夜性刺激在线看免费带字幕_助力高品质欧美狂喷水_亚洲精品日韩无码_精品无码一区二区三区蜜臀_麻豆高清国产AV_熟妇人素无码中文字幕_亚洲a级片在线观看_国产欧美日韩三区_99国产成人高清在线观看

ARTICLE DETAIL

資訊詳情

深耕商務(wù)建站與企業(yè)官網(wǎng)運(yùn)營的一線實(shí)戰(zhàn)洞察。

Undo Manager 實(shí)現(xiàn)原理:操作建模、雙棧調(diào)度與狀態(tài)快照

Undo Manager 實(shí)現(xiàn)原理:操作建模、雙棧調(diào)度與狀態(tài)快照 簡介本資源是一套面向C桌面應(yīng)用開發(fā)者的撤銷/重做Undo/Redo功能完整實(shí)現(xiàn)方案適用于文本編輯器、富文本處理工具等需狀態(tài)回溯能力的中高級(jí)項(xiàng)目。壓縮包共61個(gè)文件含28個(gè).cpp源文件如UndoManager.cpp、RETypingAction.cpp、REDropAction.cpp等承載核心操作封裝與狀態(tài)管理邏輯、31個(gè).h頭文件定義Action基類、視圖接口、文本范圍與樣式結(jié)構(gòu)等另含1個(gè)說明文檔和1個(gè)資源腳本整體僅70KB輕量但結(jié)構(gòu)嚴(yán)謹(jǐn)。目前已有155人學(xué)習(xí)下載適合希望深入理解命令模式、棧式歷史管理、細(xì)粒度操作建模打字、替換、拖放、插入對(duì)象等的開發(fā)者。代碼采用模塊化設(shè)計(jì)支持RichEdit與BetterEdit雙視圖體系涵蓋多步菜單交互MultipleUndoRedoMenu.cpp與變更日志記錄Change Log.txt可直接集成或作為教學(xué)范例剖析撤銷系統(tǒng)架構(gòu)與事件協(xié)同機(jī)制。1. Undo Manager 是什么一個(gè)被低估的“操作后悔藥”不是 UI 組件而是狀態(tài)變更的底層契約你寫了個(gè)表單編輯器用戶改了 5 行數(shù)據(jù)、刪了 2 個(gè)字段、又拖拽重排了順序——然后猛按 CtrlZ結(jié)果只撤回了最后一次拖拽前面的修改全丟了或者更糟撤回后界面沒刷新但內(nèi)部狀態(tài)已回滾下一次提交直接把臟數(shù)據(jù)發(fā)了出去。這不是前端框架的鍋是 Undo Manager 沒立住契約。Undo Manager 不是某個(gè) React 庫里的 Hook也不是 Vue 的插件它是一套對(duì)狀態(tài)變更行為建模、封裝、可追溯、可原子回滾的通用機(jī)制。它的核心價(jià)值不在“能撤銷”而在“知道誰在什么時(shí)候以什么方式改了什么并且能精準(zhǔn)還原到那個(gè)瞬間”。它適用于任何需要操作歷史、多級(jí)回退、協(xié)同編輯、調(diào)試回放或狀態(tài)快照的場景——從代碼編輯器、圖形設(shè)計(jì)工具到工業(yè) HMI 系統(tǒng)、低代碼平臺(tái)的邏輯編排畫布。如果你正在手寫history.push()或靠useState 數(shù)組堆棧硬扛撤銷邏輯那說明你還沒真正接入 Undo Manager 的抽象層它不關(guān)心 UI 怎么渲染只負(fù)責(zé)把“變更”變成可序列化、可比較、可執(zhí)行的 Operation 對(duì)象。下面我們就從零搭起這個(gè)“后悔藥”的骨架不依賴任何框架封裝直擊底層實(shí)現(xiàn)邏輯。2. 從零實(shí)現(xiàn)一個(gè)最小可用 Undo Manager用 Operation 建模變更用 Stack 管理歷史Undo Manager 的本質(zhì)是把“用戶的一次操作”翻譯成一段可逆的、帶元信息的狀態(tài)變更指令。它不能只存舊值和新值太粗暴也不能只存函數(shù)引用不可序列化、難調(diào)試。我們采用經(jīng)典的Operation 模式每個(gè)操作是一個(gè)對(duì)象包含execute()正向執(zhí)行、undo()反向回滾、redo()重做三個(gè)方法以及timestamp、label、source等元數(shù)據(jù)。所有 Operation 實(shí)例由一個(gè)雙棧undoStack redoStack統(tǒng)一調(diào)度。2.1 定義 Operation 接口與基礎(chǔ)實(shí)現(xiàn)類我們先定義 Operation 的 TypeScript 接口再給出一個(gè)通用基類BaseOperation它提供默認(rèn)的redo()實(shí)現(xiàn)即再次調(diào)用execute()并強(qiáng)制子類實(shí)現(xiàn)execute()和undo()// types.ts export interface Operation { execute(): void; undo(): void; redo(): void; timestamp: number; label: string; source?: string; // 標(biāo)識(shí)操作來源如 keyboard | mouse | api } // base-operation.ts export abstract class BaseOperation implements Operation { timestamp Date.now(); abstract execute(): void; abstract undo(): void; redo(): void { this.execute(); // 默認(rèn)重做 再執(zhí)行一次 } }提示redo()默認(rèn)復(fù)用execute()是常見做法但某些場景如異步操作需重載。此處保持簡潔后續(xù)會(huì)說明如何擴(kuò)展。2.2 構(gòu)建 UndoManager 核心類雙棧調(diào)度與邊界控制UndoManager類不持有任何業(yè)務(wù)狀態(tài)只管理 Operation 實(shí)例的生命周期。它維護(hù)兩個(gè)數(shù)組棧undoStack待撤銷的操作和redoStack已撤銷、待重做的操作。關(guān)鍵約束有三最大歷史深度限制避免內(nèi)存爆炸合并連續(xù)操作如連打 10 個(gè)字符應(yīng)合并為一個(gè)“輸入文本”操作而非 10 個(gè)禁止空操作入棧execute()后若狀態(tài)未變不記錄。// undo-manager.ts export class UndoManager { private undoStack: Operation[] []; private redoStack: Operation[] []; private maxHistory 200; constructor(options: { maxHistory?: number } {}) { this.maxHistory options.maxHistory ?? 200; } // 執(zhí)行操作先運(yùn)行再入 undoStack清空 redoStack execute(op: Operation): void { op.execute(); this.undoStack.push(op); this.redoStack []; // 一旦新操作發(fā)生重做歷史失效 this.trimStacks(); } // 撤銷彈出 undoStack 頂部執(zhí)行 undo()推入 redoStack undo(): void { if (this.undoStack.length 0) return; const op this.undoStack.pop()!; op.undo(); this.redoStack.push(op); } // 重做彈出 redoStack 頂部執(zhí)行 redo()推入 undoStack redo(): void { if (this.redoStack.length 0) return; const op this.redoStack.pop()!; op.redo(); this.undoStack.push(op); } // 合并最近操作用于連續(xù)輸入等場景 mergeLast(operation: Operation): void { if (this.undoStack.length 0) return; const last this.undoStack[this.undoStack.length - 1]; // 此處需業(yè)務(wù)判斷是否可合并例如同 label、同 source、時(shí)間間隔 300ms if ( last.label operation.label last.source operation.source Date.now() - last.timestamp 300 ) { // 調(diào)用業(yè)務(wù)自定義的 merge 方法需 Operation 子類實(shí)現(xiàn) if (typeof (last as any).merge function) { (last as any).merge(operation); } } } // 清理?xiàng)13珠L度不超過 maxHistory private trimStacks(): void { if (this.undoStack.length this.maxHistory) { this.undoStack this.undoStack.slice(-this.maxHistory); } if (this.redoStack.length this.maxHistory) { this.redoStack this.redoStack.slice(-this.maxHistory); } } // 獲取當(dāng)前可撤銷/重做數(shù)量供 UI 顯示按鈕狀態(tài) get canUndo(): boolean { return this.undoStack.length 0; } get canRedo(): boolean { return this.redoStack.length 0; } get historySize(): number { return this.undoStack.length; } }這段代碼實(shí)現(xiàn)了 Undo Manager 的骨架。注意mergeLast()并未強(qiáng)制要求所有 Operation 都實(shí)現(xiàn)merge()而是用typeof安全檢查——這是為了保持基類輕量把合并邏輯下沉到具體業(yè)務(wù) Operation 中。trimStacks()在每次execute()后調(diào)用確保內(nèi)存可控canUndo/canRedo是典型的 UI 友好屬性方便綁定按鈕禁用狀態(tài)。2.3 實(shí)戰(zhàn)為表單字段創(chuàng)建 TextChangeOperation現(xiàn)在我們寫一個(gè)真實(shí)可用的 Operation 子類TextChangeOperation用于處理輸入框內(nèi)容變更。它需記錄字段名、舊值、新值并支持合并連續(xù)輸入時(shí)將多次setValue(a) → setValue(ab) → setValue(abc)合并為一次setValue(abc)。// operations/text-change-operation.ts import { BaseOperation } from ../base-operation; export interface TextChangePayload { field: string; oldValue: string; newValue: string; } export class TextChangeOperation extends BaseOperation { constructor( public payload: TextChangePayload, public label 修改文本, public source keyboard ) { super(); } execute(): void { // 注意execute 不直接改 DOM而是調(diào)用業(yè)務(wù) setter // 這里假設(shè)有一個(gè)全局 store 或 context 提供 updateField 方法 updateField(this.payload.field, this.payload.newValue); } undo(): void { updateField(this.payload.field, this.payload.oldValue); } // 合并邏輯僅當(dāng)同字段、且新值以舊值開頭時(shí)擴(kuò)展 newValue merge(other: TextChangeOperation): void { if (this.payload.field ! other.payload.field) return; if (other.payload.newValue.startsWith(this.payload.newValue)) { this.payload.newValue other.payload.newValue; this.timestamp other.timestamp; // 更新時(shí)間戳為最新 } } } // 模擬業(yè)務(wù) setter實(shí)際項(xiàng)目中應(yīng)注入 function updateField(field: string, value: string): void { // 例如document.getElementById(field)?.value value; // 或 dispatch({ type: UPDATE_FIELD, payload: { field, value } }); }邏輯說明TextChangeOperation的execute()和undo()都調(diào)用同一個(gè)updateField()但傳入不同值。這保證了正向/反向行為完全對(duì)稱。merge()方法判斷other.newValue是否以當(dāng)前newValue開頭——這是連續(xù)輸入的典型特征a → ab → abc滿足則更新newValue和timestamp實(shí)現(xiàn)視覺上的“一次輸入”效果。參數(shù)說明field是字段唯一標(biāo)識(shí)如user_nameoldValue/newValue是字符串快照label和source用于 UI 展示和審計(jì)。3. 與 React 深度集成用自定義 Hook 封裝狀態(tài)聯(lián)動(dòng)避免 useEffect 嵌套陷阱Undo Manager 本身是框架無關(guān)的但落地時(shí)必須與 UI 框架的狀態(tài)更新機(jī)制對(duì)齊。在 React 中常見錯(cuò)誤是在useEffect里監(jiān)聽輸入事件并execute()但execute()觸發(fā)的updateField()又導(dǎo)致組件重渲染進(jìn)而再次觸發(fā)useEffect形成無限循環(huán)。正確解法是分離“操作捕獲”與“狀態(tài)更新”用 ref 緩存當(dāng)前值用useCallback固化操作創(chuàng)建邏輯讓 UndoManager 成為純命令總線。3.1 創(chuàng)建 useUndoableState Hook接管 useState 并注入 Undo 能力我們不替換useState而是在其之上疊加一層“可撤銷狀態(tài)”。該 Hook 返回[value, setValue, undo, redo, canUndo, canRedo]其中setValue是增強(qiáng)版自動(dòng)記錄 Operation。// hooks/use-undoable-state.ts import { useState, useRef, useCallback } from react; import { UndoManager } from ../undo-manager; import { TextChangeOperation } from ../operations/text-change-operation; export function useUndoableStateT( initialValue: T, options: { manager?: UndoManager; maxHistory?: number } {} ) { const [value, setValue] useStateT(initialValue); const currentValueRef useRefT(initialValue); const manager options.manager || new UndoManager({ maxHistory: options.maxHistory }); // 增強(qiáng)版 setValue生成 Operation 并執(zhí)行 const undoableSetValue useCallback( (newValue: T | ((prev: T) T), source: string user) { const resolvedValue typeof newValue function ? (newValue as (prev: T) T)(currentValueRef.current) : newValue; // 僅當(dāng)值真正改變時(shí)才記錄操作防抖 避免空操作 if (Object.is(resolvedValue, currentValueRef.current)) return; const payload { field: root, // 單值場景用 root 標(biāo)識(shí) oldValue: currentValueRef.current as unknown as string, newValue: resolvedValue as unknown as string, }; const op new TextChangeOperation(payload, 設(shè)置值, source); manager.execute(op); // 同步更新本地 ref 和 state currentValueRef.current resolvedValue; setValue(resolvedValue); }, [manager] ); // 撤銷/重做方法直接代理給 manager const undo useCallback(() manager.undo(), [manager]); const redo useCallback(() manager.redo(), [manager]); return [ value, undoableSetValue, undo, redo, manager.canUndo, manager.canRedo, ] as const; }參數(shù)說明initialValue是初始狀態(tài)options.manager允許復(fù)用外部 UndoManager 實(shí)例如跨多個(gè)表單項(xiàng)共享歷史source參數(shù)用于區(qū)分操作來源鍵盤輸入、API 回填、程序初始化等便于后續(xù)審計(jì)或條件過濾。關(guān)鍵點(diǎn)在于currentValueRef它始終保存最新值undoableSetValue比較的是 ref 值而非 state 值避免因setState異步性導(dǎo)致的誤判。Object.is()用于嚴(yán)格相等判斷支持 NaN、Object 等。3.2 在組件中使用一個(gè)可撤銷的搜索框現(xiàn)在我們寫一個(gè)真實(shí)組件展示如何用useUndoableState構(gòu)建帶撤銷能力的輸入框// components/SearchInput.tsx import React, { KeyboardEvent } from react; import { useUndoableState } from ../hooks/use-undoable-state; export function SearchInput() { const [query, setQuery, undo, redo, canUndo, canRedo] useUndoableStatestring(); const handleKeyDown (e: KeyboardEventHTMLInputElement) { if (e.key Enter) { // 模擬搜索提交 console.log(Search:, query); // 提交后可選擇清空 redoStack防止用戶誤重做搜索 // manager.redoStack []; } }; return ( div classNamesearch-container input typetext value{query} onChange{(e) setQuery(e.target.value, keyboard)} onKeyDown{handleKeyDown} placeholder輸入搜索關(guān)鍵詞... / div classNamecontrols button onClick{undo} disabled{!canUndo} 撤銷 (CtrlZ) /button button onClick{redo} disabled{!canRedo} 重做 (CtrlY) /button span classNamehistory-count 歷史: {canUndo ? ● : ○} {canRedo ? ● : ○} /span /div /div ); }邏輯說明onChange事件中調(diào)用setQuery(value, keyboard)自動(dòng)觸發(fā)TextChangeOperation創(chuàng)建與執(zhí)行onKeyDown捕獲 Enter 提交此時(shí)query是最新值可安全用于 API 調(diào)用。按鈕disabled屬性直連canUndo/canRedoUI 狀態(tài)與 Manager 內(nèi)部棧完全同步。這里沒有useEffect沒有競態(tài)所有狀態(tài)流轉(zhuǎn)都由setQuery這一個(gè)入口驅(qū)動(dòng)。3.3 處理復(fù)雜狀態(tài)用 ObjectChangeOperation 管理嵌套對(duì)象單值夠用但表單常是對(duì)象結(jié)構(gòu)如{ name: , email: }。我們擴(kuò)展一個(gè)ObjectChangeOperation它記錄整個(gè)對(duì)象的深拷貝快照并支持按路徑更新// operations/object-change-operation.ts import { BaseOperation } from ../base-operation; import { deepClone, setByPath, getByPath } from ../utils; export interface ObjectChangePayload { path: string; // 如 user.profile.name oldValue: unknown; newValue: unknown; snapshotBefore: Recordstring, unknown; snapshotAfter: Recordstring, unknown; } export class ObjectChangeOperation extends BaseOperation { constructor( public payload: ObjectChangePayload, public label 修改對(duì)象字段, public source user ) { super(); } execute(): void { // 更新全局狀態(tài)樹假設(shè)存在 updateStateTree 函數(shù) updateStateTree(this.payload.path, this.payload.newValue); } undo(): void { // 恢復(fù)整個(gè)快照更安全避免路徑更新遺漏 restoreStateTree(this.payload.snapshotBefore); } merge(other: ObjectChangeOperation): void { // 同路徑、時(shí)間相近則更新 newValue 和快照 if ( this.payload.path other.payload.path Date.now() - this.timestamp 300 ) { this.payload.newValue other.payload.newValue; this.payload.snapshotAfter other.payload.snapshotAfter; this.timestamp other.timestamp; } } } // 輔助函數(shù)簡化版 function updateStateTree(path: string, value: unknown): void { // 實(shí)際項(xiàng)目中對(duì)接 Zustand / Redux / Jotai 等 } function restoreStateTree(snapshot: Recordstring, unknown): void { // 用 snapshot 替換整個(gè)狀態(tài)樹 }使用提示ObjectChangeOperation通過snapshotBefore/After記錄完整狀態(tài)比單字段更新更魯棒尤其當(dāng)字段間有聯(lián)動(dòng)計(jì)算時(shí)。path支持點(diǎn)號(hào)分隔user.address.citysetByPath和getByPath是標(biāo)準(zhǔn)工具函數(shù)。合并邏輯同樣基于路徑和時(shí)間窗口確保同一字段的連續(xù)修改被聚合。4. 避坑指南Undo Manager 實(shí)戰(zhàn)中 4 個(gè)高頻翻車點(diǎn)與血淚修復(fù)方案Undo Manager 看似簡單但落地時(shí)極易因狀態(tài)耦合、時(shí)機(jī)錯(cuò)位、邊界遺漏而集體翻車。以下是某跨平臺(tái)低代碼平臺(tái)在灰度階段踩過的 4 個(gè)真實(shí)坑每條都附帶可立即復(fù)用的修復(fù)代碼。4.1 現(xiàn)象撤銷后 UI 未更新但內(nèi)部狀態(tài)已回滾 → “界面黑匣子”原因undo()執(zhí)行了op.undo()但該方法只修改了業(yè)務(wù)狀態(tài)如 Redux store未觸發(fā) React 組件重新渲染。常見于op.undo()直接修改了ref.current或全局變量卻忘了通知 UI。解決所有undo()和execute()方法內(nèi)必須顯式觸發(fā)狀態(tài)更新。不要依賴“狀態(tài)變了 UI 自動(dòng)更新”的幻覺。修復(fù)方案是在undo()中調(diào)用setValue()或setState就像execute()一樣// ? 錯(cuò)誤只改 ref不觸發(fā)渲染 undo(): void { this.payload.fieldRef.current this.payload.oldValue; // UI 不知道 } // ? 正確調(diào)用狀態(tài) setter保證 UI 同步 undo(): void { // 假設(shè)我們有注入的 setter this.setter(this.payload.field, this.payload.oldValue); }血淚經(jīng)驗(yàn)在BaseOperation基類中預(yù)留setter屬性構(gòu)造時(shí)傳入強(qiáng)制子類使用統(tǒng)一更新通道。避免任何 Operation 直接操作 DOM 或 ref。4.2 現(xiàn)象連續(xù)快速輸入如粘貼長文本導(dǎo)致撤銷粒度太細(xì)一次 CtrlZ 只刪一個(gè)字符原因TextChangeOperation的合并閾值300ms在粘貼場景下失效——粘貼是單次事件但內(nèi)部觸發(fā)了多次input事件每次input都生成獨(dú)立 Operation且時(shí)間戳相同無法合并。解決增加“批量操作標(biāo)記”。在粘貼開始前用manager.beginBatch(paste)標(biāo)記批次結(jié)束時(shí)manager.endBatch()期間所有 Operation 自動(dòng)合并為一個(gè)// undo-manager.ts 新增方法 beginBatch(label: string): void { this.batchLabel label; this.batchStartTimestamp Date.now(); } endBatch(): void { if (!this.batchLabel) return; // 將 batch 期間所有操作合并為一個(gè) CompositeOperation const batchOps this.undoStack.splice(-this.batchOpCount); if (batchOps.length 0) { const composite new CompositeOperation(batchOps, this.batchLabel); this.undoStack.push(composite); } this.batchLabel undefined; this.batchOpCount 0; }然后在輸入框onPaste事件中調(diào)用input onPaste{() { manager.beginBatch(paste); setTimeout(() manager.endBatch(), 0); // 微任務(wù)等待 input 事件完成 }} /提示setTimeout(..., 0)確保endBatch()在所有input事件處理完畢后執(zhí)行捕獲全部操作。4.3 現(xiàn)象異步操作如 API 保存后撤銷狀態(tài)回滾但服務(wù)器數(shù)據(jù)未回退 → “數(shù)據(jù)不一致”原因execute()中調(diào)用了saveToServer()但undo()只回滾本地狀態(tài)未調(diào)用revertOnServer()。Undo Manager 默認(rèn)不處理副作用需業(yè)務(wù)顯式聲明。解決擴(kuò)展 Operation 接口增加asyncUndo()方法并在UndoManager.undo()中優(yōu)先調(diào)用它export interface AsyncOperation extends Operation { asyncUndo?(): Promisevoid; // 可選僅異步操作需實(shí)現(xiàn) } // 在 UndoManager.undo() 中 async undo(): Promisevoid { if (this.undoStack.length 0) return; const op this.undoStack.pop()!; if (asyncUndo in op typeof op.asyncUndo function) { await op.asyncUndo(); // 等待服務(wù)端回滾 } op.undo(); // 再回滾本地 this.redoStack.push(op); }對(duì)應(yīng)地創(chuàng)建ApiSaveOperationclass ApiSaveOperation extends BaseOperation { constructor(public apiCall: () Promisevoid) { super(); } asyncUndo(): Promisevoid { // 調(diào)用反向 API如 DELETE /drafts/{id} await fetch(/api/drafts/${this.id}, { method: DELETE }); } execute(): void { this.apiCall(); // 保存 } undo(): void { // 本地回滾如恢復(fù)草稿緩存 } }4.4 現(xiàn)象頁面刷新后 Undo 歷史丟失用戶抱怨“剛做的修改全沒了”原因UndoManager 實(shí)例是內(nèi)存對(duì)象刷新即銷毀。需持久化歷史棧到localStorage或 IndexedDB。解決在UndoManager構(gòu)造時(shí)加載歷史并在每次execute()后自動(dòng)保存。為防序列化失敗只存 Operation 的可序列化部分label,payload,timestamp重建時(shí)動(dòng)態(tài)實(shí)例化constructor(options: { maxHistory?: number; storageKey?: string; // 如 my-app-undo-history } {}) { super(options); this.storageKey options.storageKey; if (this.storageKey) { this.loadFromStorage(); } } private loadFromStorage(): void { try { const saved localStorage.getItem(this.storageKey); if (saved) { const data JSON.parse(saved); // 重建 Operation 實(shí)例需注冊(cè) Operation 類型映射 this.undoStack data.undo.map((item: any) this.deserializeOperation(item) ); this.redoStack data.redo.map((item: any) this.deserializeOperation(item) ); } } catch (e) { console.warn(Failed to load undo history, e); } } private saveToStorage(): void { if (!this.storageKey) return; try { const data { undo: this.undoStack.map(op this.serializeOperation(op)), redo: this.redoStack.map(op this.serializeOperation(op)), }; localStorage.setItem(this.storageKey, JSON.stringify(data)); } catch (e) { console.warn(Failed to save undo history, e); } } // 在 execute() 末尾調(diào)用 this.saveToStorage();注意serializeOperation需根據(jù) Operation 類型返回純對(duì)象deserializeOperation根據(jù)type字段 new 對(duì)應(yīng)類。這是典型的“序列化-反序列化”模式務(wù)必測試邊緣 case如函數(shù)、undefined、Date。5. 進(jìn)階技巧用 Operation 快照做狀態(tài)調(diào)試回放把 Undo Manager 變成黑盒分析儀Undo Manager 的最大隱藏價(jià)值不是讓用戶按 CtrlZ而是讓你在生產(chǎn)環(huán)境回放任意一次用戶操作流精準(zhǔn)定位 UI Bug。我們把每個(gè) Operation 的execute()和undo()執(zhí)行前后自動(dòng)采集狀態(tài)快照snapshot構(gòu)建一條可追溯的“狀態(tài)時(shí)間線”。這不需要額外埋點(diǎn)只需在UndoManager.execute()中插入快照邏輯。5.1 在 Operation 執(zhí)行前后自動(dòng)采集狀態(tài)快照我們擴(kuò)展UndoManager增加captureSnapshot()方法它接受一個(gè)stateGetter函數(shù)返回當(dāng)前業(yè)務(wù)狀態(tài)并在execute()前后各調(diào)用一次// undo-manager.ts export class UndoManager { // ... 其他屬性 private snapshots: Array{ timestamp: number; before: unknown; after: unknown; operationLabel: string; } []; constructor(options: { maxHistory?: number; stateGetter?: () unknown; // 業(yè)務(wù)狀態(tài)獲取函數(shù) } {}) { this.stateGetter options.stateGetter; } execute(op: Operation): void { // 執(zhí)行前快照 const before this.stateGetter?.() ?? null; op.execute(); // 執(zhí)行后快照 const after this.stateGetter?.() ?? null; this.snapshots.push({ timestamp: Date.now(), before, after, operationLabel: op.label, }); // 入棧... this.undoStack.push(op); this.redoStack []; this.trimStacks(); } // 獲取指定索引的操作及快照用于調(diào)試面板 getSnapshotAt(index: number): { op: Operation; snapshot: NonNullabletypeof this.snapshots[number] } | undefined { if (index 0 || index this.snapshots.length) return; return { op: this.undoStack[index], snapshot: this.snapshots[index], }; } }關(guān)鍵點(diǎn)stateGetter是業(yè)務(wù)方傳入的函數(shù)例如() store.getState()或() ({ form: formData, errors: validationErrors })。它必須是純函數(shù)無副作用??煺沾鎯?chǔ)為輕量對(duì)象before/after是執(zhí)行前后的狀態(tài)副本可用于 diff 分析。5.2 構(gòu)建簡易調(diào)試面板可視化狀態(tài)變化鏈我們寫一個(gè) React 組件列出所有歷史操作并允許點(diǎn)擊任一操作將應(yīng)用狀態(tài)“跳轉(zhuǎn)”到該時(shí)刻類似 Chrome DevTools 的 replay// components/DebugReplayPanel.tsx import React, { useState, useEffect } from react; import { UndoManager } from ../undo-manager; interface SnapshotItem { timestamp: number; before: unknown; after: unknown; operationLabel: string; } export function DebugReplayPanel({ manager }: { manager: UndoManager }) { const [snapshots, setSnapshots] useStateSnapshotItem[]([]); const [activeIndex, setActiveIndex] useStatenumber | null(null); // 訂閱快照更新假設(shè) manager 提供 onSnapshot 事件 useEffect(() { const unsubscribe manager.onSnapshot((snapshot) { setSnapshots(prev [...prev, snapshot]); }); return unsubscribe; }, [manager]); const jumpTo (index: number) { if (index 0 || index snapshots.length) return; const target snapshots[index]; // 這里調(diào)用業(yè)務(wù) restoreState(target.before) 方法 restoreState(target.before); setActiveIndex(index); }; return ( div classNamedebug-panel h3狀態(tài)回放面板/h3 div classNametimeline {snapshots.map((snap, i) ( div key{i} className{step ${activeIndex i ? active : }} onClick{() jumpTo(i)} span classNamelabel{snap.operationLabel}/span span classNametime{new Date(snap.timestamp).toLocaleTimeString()}/span div classNamediff {/* 可接入 react-diff-view 顯示 before/after 差異 */} {JSON.stringify(snap.before).length 50 ? ${JSON.stringify(snap.before).slice(0, 50)}... : JSON.stringify(snap.before)} → {JSON.stringify(snap.after).length 50 ? ${JSON.stringify(snap.after).slice(0, 50)}... : JSON.stringify(snap.after)} /div /div ))} /div /div ); } function restoreState(state: unknown): void { // 實(shí)際項(xiàng)目中調(diào)用 store.replaceState(state) 或類似方法 }效果當(dāng)用戶報(bào)告“我在第 3 步點(diǎn)擊按鈕后頁面空白”你打開調(diào)試面板找到第 3 步點(diǎn)擊jumpTo(3)UI 瞬間回到那個(gè)時(shí)刻你就能看到空白頁面并 inspect 元素、查 console。這比看日志快 10 倍。5.3 生產(chǎn)環(huán)境安全開關(guān)快照功能默認(rèn)關(guān)閉按需啟用快照采集有性能開銷深拷貝、內(nèi)存占用絕不能在生產(chǎn)環(huán)境默認(rèn)開啟。我們?cè)赨ndoManager中添加enableSnapshot()方法并用環(huán)境變量控制// undo-manager.ts class UndoManager { private isSnapshotEnabled false; enableSnapshot(stateGetter: () unknown): void { // 僅在開發(fā)環(huán)境或特定 feature flag 下啟用 if (process.env.NODE_ENV development || window.DEBUG_UNDO) { this.stateGetter stateGetter; this.isSnapshotEnabled true; } } execute(op: Operation): void { if (this.isSnapshotEnabled this.stateGetter) { // 執(zhí)行快照邏輯 } // ... 其余邏輯 } }然后在應(yīng)用入口// main.tsx const manager new UndoManager(); if (process.env.NODE_ENV development) { manager.enableSnapshot(() store.getState()); }最后一句我在線上系統(tǒng)跑了兩年 Undo Manager最大的教訓(xùn)是——永遠(yuǎn)不要相信“操作很簡單不用管歷史”的想法。哪怕只是一個(gè)開關(guān)按鈕的 toggle一旦用戶需要“剛才我關(guān)掉的那個(gè)現(xiàn)在想打開回來”你就得有一套完整的、可驗(yàn)證的、帶快照的 Undo 機(jī)制。它不是錦上添花而是交互健壯性的底線。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取
返回列表
PREV
查看更多資訊
NEXT
返回資訊列表
秋霞一集毛片观看| 精品人妻一区春色| 亚洲91综合| 国产高清精品一区二区三区毛片| 亚洲AV无码天美传媒一区| 久久中久文96| 国产精品久久久久久久毛片1| 91色艳| 亚州高清AV| 中国亚洲呦女专区| 噜噜瑟| 日本在线激情一区二区三区| 有码专区最新中文字幕有码| www.婷婷五月天| 美女十八禁| 久操不卡视频| 久久久久久久97| 综合久| 夜夜欢天天干| 天堂九九九九九九九九九| 老熟乱一区二区三区四区| 天天操天天舔| 蜜臀AV午夜精品久| 艳美熟妇先锋一二三区| 日本黄色精品专区网站| 熟妇高潮一区二| 人妻中文字幕精品无码 | 美女91网| 婷婷五月天成人网| 国产精品天堂| 射丝袜高跟鞋99| 91综合中文字幕| 黑丝少妇麻豆| 日韩欧美中文日韩欧美色| 97超视频在线观看| 久操不卡视频| 日韩亚洲欧美中文字幕| 色婷婷蜜臀av| 天天看,天天做| 在线五区| 男人的天堂,欧美亚洲另类国产日韩,日本高清一区二区 | 亚洲国产精品有声| 激情视频图片| 精品九九九九九九九九九| 国产成年精品高清在线观看91| 夜夜做夜夜爽精品视频| 日本色婷婷| 91大神精品长腿在线观看网站| 色爱综合网欧美| 日韩中文字幕精品一区在线| 青青草原成人| 九热大香蕉| 日本免费中文字幕在线| 91欧美少妇| 日韩精彩免费| 国产精品午夜高潮呻吟久久av| 夜夜夜爽www精品视频| 五月婷婷丁香六月| 日韩AV无码中文一区二区| 久久精品国产亚洲av水密被窝| 91 丝袜在线| 日本九九九九| 操逼www.| 少妇被c 黄 免费观看| 樱花草社区www中国| www..com操老师| 91色宗合| 精品日日人妻| 四虎在线免费视频| 天天插夜夜爽| 99在线视频播放| 白嫩嫩一区| 911粉嫩人妻| 久久产精品一区二区三区电影| 久草在| 激情综合五月| 黄网在线播放| 精品一区二区成人| 被操高清无码视频| 97天堂| 久久偷偷色综合蜜桃| 思思热在线视频精品| 国产欧美精选自拍一区| 东京热一区二区中文字幕| 欧美亚洲韩国视频十五区| 精品久久久av| 超碰中文字幕人妻草一区| 国产精品色| 首页中文字幕中文字幕免费| 亚洲AV无码乱码| 91 丝袜在线播放| 97在线播放| 一区二区视频你懂的| 亚洲少妇喷视频看| 少妇滛荡视频| 欧美一级做a爰片免费视频| 久久男人精品| 家庭乱伦性爱av| 欧美日韩性爱电影在线| 婷婷超| 亚洲国产97| 亚洲激情在线| 床戏久久久av一区二区麻豆| 91久久久久| 久污| 人人妻人人玩人人澡人人爽| 人妻少妇精品视频一区二区三区| 99热在线只有精品| 久久久精品九| 成人久久久| 欧美日韩欧美| 1240青青草一区二区三区视频天爱| 精品人妻一区二区三区蜜桃视频| 精品人妻一区二区三区不卡断| 欧美丝袜美女电影一二三四区| 大香蕉琪琪日本女优不卡| 伊人久久大香线综合无码| 亚洲宗合电影| 日日噜噜夜夜久久亚洲一区二区 | 91九色精品熟女内射| 蜜臀AV网站| 国产精品蜜乳AV| 在线观看亚洲专区| 亚洲精品视频在线| 极品白嫩福利在线| 久久久青草青青国产亚洲免观精品高清完整版_97久久综合区小说区图片区,国精品 | 九九成人| 国产馆极品诱惑| 玖玖爱综合| 国产有码一区| 国内一区二区三区| 久久精品无码专区| 懂色中文一区二区三区 | 精品日韩产品在线,日韩在线不卡视频,欧美日韩免费专区/久, | 国产精品日日摸夜夜添骚逼| 亚洲精品一二牛牛| 首页中文字幕中文字幕免费| 亚洲天堂人妻一区二区| 大香蕉伊在线久草麻豆天堂故事| 久久是精品| 亚洲资源站| 久久伊人最新网址视频| 熟女五十路一区二区三| 久热9| 国产久久视频| 97欧美色| 亚洲AV麻豆Aⅴ无码电影一| 国产曰批免费观看久久久| 日本操BAV| 中文字幕一区二区三区蜜臀| 97精品97久久| 蜜臀视频网站| 人妻乱仑一区二区三区| 日本特黄f c2| a片亚洲一本通视频| 在线天堂资源亚洲| 男女激情黄色网址| 男人下部插入女人下部| 天美传媒精品久久视频| 岛国免费黄色网址| 久久精品免费| 97se亚洲综合自| 精品一区二区在线针对华人免费观看这里只有精品免费观看 | 久久风骚城市| 少妇高潮流水av免费| 天天肏夜夜肏| 2026国产精品视频| 偷拍偷窥与盗摄视频专区| 韩国三级理论在线| 在线97在线| 亚洲丝袜少妇在线| 能直接看AV的网站| 久久一区,青青青青草视频在线播放| 人人做天天爱| 五十路成人在线视频二区三区| 欧亚乱色熟女一区二区| 欧美三级免费伊人| 熟妇一区二区| 九九热免费视频| 亚洲丰满很很操| 亚洲欧美国产中文视频| 人人色97| 96超碰网| 黄片直播三级黄片两女一男| 麻豆成人av| WWW.操逼.COM| 亚洲97p| 激情欧美97| 一区二区三区精品视频| 久9综合在线| 成人性爱av| 麻豆精品久久久久久久| 国产强奸乱伦无码视频| 欧美久久九九| 97这里只有精品| 熟妇人妻精品一区二区视频色欲| 久久香蕉国产线看观看亚洲女人 | 欧美狠狠干| 伊人99热| 国产97av| 日本午夜福利视频| av爱爱爱| 久久精品老司| 亚洲激情综合另类男同| 天天综合网入口~91| 乱伦熟女论坛| 天天干人妻| 国产一区二区三三视频| 9ⅰ久久久天天| 亚洲一区二区三区欧美日韩| 国内成人圈中文字幕无码视频| 福利操逼| 亚洲麻豆18发?| 欧美一级A片不卡视频。| 激情终合网| 秋霞一集毛片观看| 色拍偷亚洲| 日本福利二区视频| 免费在线视频97| 偷拍伦理视频| 国产无码久久高清| 嗯嗯啊好大| 激情视屏国产乱伦强奸| 91麻豆天美国产欧美高潮| 国产精品亚洲免费| 亚洲欧美人妻| 极品AV网站在线观看| 精品十三区| 人妻系列无码专区中文有码| 夜夜欧美 | 久久久精品成人国产| 欧美激情一| julia高潮后不停追击中出| 婷婷激情四射| 一区二区三区成人| 澳门人妻久久| 国产一区二区三区精品观看啪| 日本精品加勒比海一区| 欧美 亚洲 制服 精品| 日韩无码视频黄色| 欧美另类天堂| 美女主播色欲91抠b在线播放| 国产精品嫩草影院免费| 精品一区二区三区18| 蜜桃精品一区二区三区ww| 美女熟妇色| 欧美偷偷网| 日韩乱码Av| 91午夜无码| 香蕉精品二区二区 | 久久久久久久久成人av解说| 9118禁| 尤物视频网 刘玥| 九九在线精品| 人摸人人操人| 久久一级无码精品毛片6| 亚洲一区二区AV| 色欧洲97| 老司机久久| 欧美性天天影视| 综合色99| www国产天美久久久| 囯产精品久久久久久久久久二区三区| 亚洲精品国产熟女久久久| 日韩免费人妻色情网站| 亚洲最大网站av| 天天谢天天干| 乱欲视频| 综合久久9| 春色综合免费| 亚洲drav色图| 91国精产品| 久久e6只有精品| 丁香五月色| 熟女五十路一区二区三| 中文AV制服乱伦| 天天干18禁| 97 国产精品| SUV一区二区在线看| 欲色影视综合吧| 男人天堂久久精品| 欧美|91色综合| 日本久久天堂| 久久久婷婷婷| 女同在线视频一区| 亚洲天堂东京热| 一本道综合色图| 免费A V在线| 亚洲中文字母在线播放| 亚洲中文日韩欧美大香蕉视频| 一级做a爰片性色毛片久久| 天天伊人| 深夜福利黄片| 色哟哟 日韩精品| 免费精品无码一级毛片牛牛影视| 天天影视亚洲| 啊啊啊在线观看| 国产91 丝袜在线播放00-百度| 欧亚 另类 久| 三级色影综合网| 国产精品网址| 亚洲男人综合| 国产精品成人蜜臀AV在线| 天天视频黄网站| 亚洲日韩精品一区视频在线| 色官网在线| 久久久婷婷婷| 一区二区激情国产熟女| 囯产精品一区二区三区线|亚洲人成无码网WWW动漫|国产精品免费一级... | 最新中文字幕精品在线| 国产一区二区三区导航| 啪啪啪大香蕉| 偷拍欧美激情| 亚洲性少妇| 激情久久日韩精品中文字幕麻豆| 人妻精品一区二区三区| 精品无码久久久久久久杏吧| 91久久九九精品国产综合| 超碰在线欧美性爱激情| 一区二区三区四区免费视频| 国产热RE99久久6国产精品首| 岛国成人av在线播放网址| 亚洲丁香花色| 99re69| 日本欧美m v精品网站加| 亚洲怡春院| 夜夜爽夜夜高潮夜夜爽| 欧美另类精品xxxx| 激情五月综合| 天天夜夜久久| 欧美视频一区二区三区| 2021国产成人精品久久| 综合久久久久久久综合网| 久久草在线综合视频| 久久一二三四五六七八九区区区 | 婷婷中文字幕| 一区麻豆 高清中文字幕| 少妇精品久久久| 东北女人高潮视频| 欧美一区二区三区四区综合| 亚洲色五月| 日韩精品人妻中文字幕久久久| 伊人午夜福利视频| 欧美性生活男人的天堂| 日韩性爱播放| 久久香蕉超碰97国产精品| 久久久极品| 人人操人人色人人摸| 秋霞欧美性爰视频| 伦在线97| 国产JDAV无码视频在线观看| 超碰人妻在线| 亚洲人码13| 中文字幕丰满人妻日本| 亚洲精品日日夜夜52| 日少妇亚洲版| 亚洲无码 国产无码| 极品美女福利在线观看| 日韩AV一区二区三区三州三州| 国内一区二区三区| 成 人片 黄色大片| 91逼逼女人91| 丰满人妻一区二区三区四区| 亚洲成人一区二区精品| 97香蕉人人乳| 日本爽爽爽爽爽爽免费视频| 日韩有码一区三区| 日本道人妻久久久在线不卡色视频| 日本精品中文字幕视频| 久久色人体| aV中文麻| 亚洲第一成人影院色播| 久久久久大香青草精品综合| 日韩精品字幕| 天天看综合网| 欧美性天天| 欧美日韩成人| 78精品在线| 青青草天天亲夜夜操网| 日本精品九九九| 国产精品女生av| 91 丝袜在线| 最新日本中文字幕| 97干日韩| 无码久久亚洲高清,| 国产在线76页| 久久夜夜| 熟女中出视频| 国产精品不卡一区二区三区av| 色爽爽文学| 欧美色女人| 九九热九九热| 美女裸体无遮挡永久免费观看网站 | 91精品国产91久久青草| 熟女丰满人妻一区| 大学生美女口爆| 久久久999日本大片| 日韩超碰97| 免费观看有码高清视频| 久久免费少妇| 一区麻豆 高清中文字幕| 性色AV网站| 欧美欲色| 97人妻免费中文字幕| 玖玖超碰熟| 免费视频a级毛片免费视频| 美女黄页网站| 日本一区99| 日韩三A大片在线观看| 日日夜夜模| 久久精视频美日韩在线视频| 97超碰9| 欧美亚洲第1页| 国产精品点击进入在线影院| 伊人在线大香蕉视频久久| 亚洲一区中文字幕一区| 91w欧美| 天天弄欧美| 亚洲啪啪视频一区二区| 九九九九亚洲| 人妻一区久久二区三区色播| 新婚人妻扶着粗大强行坐下| 黄色成年| 综合 青草 伊久久 影院 综合| 人人妻人射| 99精品无码| 高清不卡视频| 人人干人人操人人..com| 久久久久白虎| 日韩激情视频| 亚州精人品大香蕉| 久久久免费一级黄片| 97超碰精品图片| 综合激情97| 久久精品亚洲成a人天堂| 久久高潮妇女视频| 亚洲污污网站| 久久久精品国产亚洲伊人| 一区二区三区黄色片a| 日日躁狠狠躁天天躁精品| 丰满少妇一区二区三区四区观看| 免费一级毛片在线视频观看| 日韩国产欧美伦理在线| 97爱综合| 91精品91久久久久77777| 亚洲码和欧洲精品激情系列| 色播丁香| 91小视频| 久综合国内精品自在自线| 九九九九九九九九九国产精品 | 91蜜臀人妻中文字幕在线| 婷婷激情一区二区三区俺也去| 92午夜免费福利视频| 无码高清操逼| 特污精品女优骚货黄色视频在线免费观看| 操亚州| 久无码| 精品人妻伦一二三区久久| 亚洲va有码在线天堂| 天美传媒婬乱| 免费视频一二三区| 欧美啪啪啪91| 在线观看啊啊啊啊啊| 97 九色| 九九久久久久久爱| 亚洲美乱| 中文字幕啊啊啊在线观看视频| 日韩人成网站在线播放| 国产av波波国产精品| 久96热在线观看视频| 99在线观看视频在线高清| 久久久专区| 欧美色天堂网在线视频| 亚洲淫乱骚妇AV| 91女人的网站| 九九玖玖精品| 国内精品久久国产,www香蕉久久五月丁香,亚洲欧美日韩精品永久在线,日本精品一 | 少妇二级| 激情第四色| 九九香蕉网| 日韩成人网址| 丁香五月天激情| 国产精品久久久三级无码| 国产精品婬乱一级毛片彝族| 秋霞免费AV| 国产亚州高清国产拍精| 精品在线观看视频在线| 国产精品无码论坛| 色官网在线| 欧美成人性爱视频大全| 国产又色又爽又舒服的三级视频| 91强奸乱轮| 无码抄逼网| 国产九九九九九九九九| 日韩啪啪视频| 97操97色| 成人一区二区三区四区| 日韩色图 一区二区| 久久9999| 中文字幕在线免费观看 | 精品少妇人妻av久久免费| 日韩精品在线观看观看| 亚洲乱码国产乱码精网站| 人人妻人人狠人人| 男人天堂2019亚洲| 欧美熟妇人体| 久久人妻少妇| 人妻丝袜二区| 国产在线观看91精品一区| 欧美日韩日产免费网站看| 亚洲av综合色区无码一| 激情六月婷婷| 九一综合精品视品av| 日韩精品操少妇| 加勒比久久av| 国产一国产一级毛片古装| 国产女人高潮嗷嗷嗷叫小说| 日韩乱插| 欧美一区二区情色| 国内外毛片在线观看| 人人操人人肉久久精品| 性爱视频免费网址| 国产一区二区啪啪视频| 婷婷三区| 啊啊啊轻点在线观看| av天堂电影网| 日本高清_区二区三区| 国产按摩一区二区三区| 亚州综合色图| 91中出视频| 2020中文在线一区二区三区| 婷婷五月av| 91夜色chaopeng| 青青草综合在线| 国产家庭乱伦性爱视频| www五月| 久久久久亚洲精品| 精品在线蜜臀| 久久‘黄片视频| 96AV久久久| 青青草华人在线欧美在线| 国产1024在线播放| 91九色精品熟女内射| 偷拍超碰| 天天影视色香欲综合网小说| 久久xxxx| 97国产精品久久久久| 亚洲无码com| www.久久爱| 91精品婷婷国产综合久久竹菊| 91亚洲欧美激情| 亚州AV无码国产精品| 亚洲国产剧情少妇激情| 天堂性色| 国产女人成人精品视频| 人妻熟妇久草在线| 100啪啪视频大全| 一区二区三区精品黑丝白丝酒店对鸡 | www.av不卡中文字幕| 四虎午夜影院| 九九热九九热| 欧美天天综合网版| 激情网五月天| 嗯嗯啊啊啊好爽| 国产九九九九九九九九| 欧美天堂第二区| 影音先锋少妇| 又摸又舔在线观看网站| 乱操乱伦AV| 久久9久9久99久9久9| 啊啊啊啊啊好多水| 久久午夜伦| 亚洲不雅视频1区二区| 国产免费一区在线观看| 蜜臀久久99精品久久久久电影| 手机在线人成免费视频| 欧美色综合图片| 国产精品久久久久亚洲av| 午夜性| 免费看黄片现成| 欧美激情 日韩精品| 亚洲无码精品AV久久久| 一区二区三区四区五区高清无码永久视频 | 91天堂网| 国产女上位好爽在线| 97综合在线| 欧美日韩色图片| 国产内射爽爽大片| 美腿丝袜偷拍亚洲欧美| 97在线观看播放视频| 污污污8888| 秋霞无码av鲁丝片一区| 亚洲综合草草| 日日爽夜夜爽| 欧美成人免费在线观看| 国产超碰在线一区| 日本丝袜人妻内射| 啊啊啊男女| 欧苏综合色综合| 91福利网在线观看| 激情文学 亚洲图片| 欧美一级黄色18片免费看| 91蜜桃传媒精品久久久一区二区| 五月婷婷深深爱| 84YTCOM性无码| 亚洲激情天堂网| 大伊香蕉在线视频免费| 久午视频| 国产美女口爆吞精| 日本午夜久久电影| 亚洲成a人v欧美综合天堂下载| 国产精品久久久蜜臀| 国产人伦a片信息免费片| 欧美第五页| 麻豆AV一区二区| 在线观看AV片| 欧美不在线| 91美女色视频亚洲| 人妻久久久久久| 一个色导综合| 国产一区二区精品久久久不卡蜜臀| 3571色综合一区二区二区| 一级二级三级黑人无码| 亚洲素人综合| 吉田爱美AV在线| 久久人人爽人人爽人人片Ⅴ| 日本操逼视频免费| 蜜臀在线看片| 欧美色66| 国产刺激视频| 成人性爱美曰韩| 激情文学88| 综合情欲网| 亚州乱码中文字幕综合久久久| 国产人妻精品一区二区三区秋霞| 午夜美女诱惑电源网| 精品人妻一区| WWW美腿丝袜香蕉中文| 上海一级黄片| 色97国产69香蕉| 婷婷亚洲综合| 999综合网| 清纯唯美激情四射| 精品国产无码中文| 欧美少妇高潮视频| 97天天爽| 国产成人欧美一区二区三区的国产| 天天舔天天日天天射| 熟女人妻一区二区三区| 九九九国产| 午夜啊啊啊| 搡老熟女老女人老熟妇免费视频| 美女高潮国产高清| 二级毛片| 少妇天堂网络| 激情综合五月| 黄色AAAAAAAAAAA大片| 亚洲不卡一| 亚洲欧洲美腿丝袜| 性爱av在线免费观看| 91色综合| 性感美女91影视| 蜜臀在线免费观看在线免费观看| 在线观看中文av字幕| 91老熟女视频| 91蜜臀人妻中文字幕在线| 蜜臀AV成人精品蜜臀AV久久| 97在线精品| 美女写真| 在线一道啪| 强被迫伦姧在线观看无码网站| 天天操福利视频综合网站| 免费人人搞97| 国产白嫩精品久久| 插入综合网| 精品亚洲国产成人AV制服丝袜| 国产视频人人网| 怡红院成人视频| 青青草无码视频| 蜜桃久久精品一区二区三区| 日韩精品99999| 狼人久草| 亚洲日本韩国在线| 亚洲强奸乱伦影视网| 亚洲乱色熟女一区| 搡老女人老91妇女老熟女| 91综合天天| 色97综合中文字幕| 嗯嗯啊啊的视频| 亚洲色图 图片| 99精品伊人| 欧美情色男人的天堂| 亚洲丝袜B诱惑| 极品内射| 人妻熟妇久草在线| 久久久久久裸体| 日韩特一级久久| 久久久天堂| 国产精品久久久| 成年无码动漫av片无尽在线 | 91综合天天看| 色原狠狠天天天| 亚洲毛片久久| 9色国产精品一区粉嫩| 无码免费在线观看黄色片| 日韩综合成人免费视频| 亚洲第一狼人丝袜美女另类 | 茄子社区国产精品| 外国免费性情大片| 久久久久久亚洲Av无码精| 岛国色情视频在线观看| 国产又粗又长又爽又色| 激情婷婷五月天| 99xav| 久热网| 免费av在线播放二区| 国产大学生高潮在线播放| 欧美成人9797| 欧美国产操逼| 日韩一级欧美一级国产一级台湾 | 青青草伊人久久| 亚洲人综合| 成人性爱免费播放| 依人大香蕉| 日本五区不卡| 久久东京伊人一本到鬼色| 久久久影院| 亚洲风情综合网| 日本狂喷奶水在线播放212| 久久精品国产精品亚洲艾通辽熟妇| 97久久精品亚洲中六字幕| 密臀在线视频| 凹凸视频特色日本特黄| 天天天天天天天天综合| 国产捆绑一区| 久久久一级| 麻豆精品一区二区三区四区免费观看| 粉嫩av在线一区二区| 五十路熟女工口 | 婷婷五月丁香五月| 日本不卡免费二区| 亚洲乱码国产乱码精网站| 欧美精品91| 亚洲欧洲日本精品中文a∨| 丁香五月激情综合| 欧美色图91| 超碰亚洲欧美日韩无| 伊人网高清| 国产suv精品一区二区四| 国产传媒一区二区三区| 亚 欧 美 综合| 中文字幕丰满子伦无码专区在线视频最新| 99操99| 综合网 欧美| 91天堂视频| 国产网红精品| www.久久超碰| 97视频在线免费看| 九九热久久99精品re| 人人操人人射人人干| 欧美大香蕉专区网| 91熟女网| 亚洲国产一级中文综合久久天堂在线免费观看 | 欧美草草高清日韩视频| 偷拍 精品另类 凸凹了四区| 亚洲不卡三级手机播放| 亚洲男人天堂2013| 九九亚洲| 久久黄色性爱视频| 欧洲人妻视频| 日本最新1区2区3区| 色啪网| 综合久久久久久久综合网| 大香蕉一人| 91天堂视频| 中文字幕在线免费观看视频| 日韩熟女操逼| 校园激情狠狠四射| 天天内射| 美女大乳久久久久久久女人18| 美国aaaaa一级黄片| 超碰色综合| 欧美亚洲首页| 久9爱经典视频| 99RE在线视频精品,这里只有精品| 婷婷伊人| 国产精品亚洲一区二区三区四区| 国产午夜精品在线观看| 久操视频这里只有精品| 天天激色| 超碰97在线中文| 伊人国产视频| 一区二区三区男女操逼黄色小电影| 五月天色图| 免费a在线播放v| 激情网色| 色色色欧美| 国产A v无码专区| 精品国产丝袜一区二区三区乱码| 顶级少妇BT天堂| 亚洲精品亚洲人成在线麻豆| 色综九九九一区| 男女打扑克高清网站| 五月丁香综合激情| 婷婷丁香六月| 九九热视频在线观看| 91久久婷婷| 亚洲欧洲偷拍一区| 爱爱动态120秒| 国产精品91ai| 精品人妻一二三四区视频| 国产人妻精品一区二区三区秋霞| 久久综合久色欧美综合狠狠 | 欧美一区二区一级岛国大片| 久久久精品91八戒| 免费一级特黄特色大片在线观看看 | 伊人女女资源在线观看| www超碰| 污到发麻的视频 国产| 台湾大香蕉99热| 日韩精品影视| 亚洲男人的天堂va亚洲男人社| 四虎在线观看视频| 色嗨嗨在线| 神马精品视频| 久9爱经典视频| 精品人妻av区天天看片| 99精品视频在线观看免费| 国产自偷自拍一区| 天天综和| 亚洲欧美黄| 亚洲一欧洲中文字幕在线 | 九九热在线精品视频| 亚洲 欧美 小说| 蜜臀久久久99久久久久 | 大香蕉专区| 嗯嗯嗯啊啊啊在线免费观看| 亚洲黄a三级三级三级看三级| 丝袜AV一区二区三区| 日本激情免费大片| 嗯……啊…嗯嗯…啊…好舒服| 精品无码一区二区三区| 国产精品亚洲高清在线| 亚洲图片欧美91N| 99热这里都是精品| 8050午夜少妇无码| 超碰1997| 嫩草一区二区在线观看| 国产精品久久久久久久久久久久久久久久久久 | 国产精品色| 美女91网址 | 国产区在线| 91色久| 综合网亚洲在线| 免费AV播放| 清纯唯美综合亚洲| 久操精品网| 96国产精品| 天综合中文| 另类亚洲一区二区三区| 超碰 另类 欧美 | 欧美色日| 国产AV超爽| 一区三区啪啪| 青娱乐日韩无码| 亚洲超碰在线| 亚洲精品欧洲色| 九一亚洲国产免费| 91搞逼视频| 加勒比av网| 欧洲色| 精品午夜福利导航| 97国产天堂岛| 综合操逼| 亚洲美女精品九九视频| 殴美,日韩国产伦精品| 婷婷色色五月天福利| 亚州色图欧美| 伊人AAA| 夜夜操青青草| 欧美78P| 91天天爱| 五月天社区| 成人性爱视频在线看| 亚洲少妇视频| 欧亚揄拍偷拍精品视频| 蜜臀久久99精品久久久老,,| 精品久久久久久AV无码| 90后后入| 欧美 亚洲 另类 综合| 国产成人无码网站在线视频| 大色综合| 殴美色网| 中文字幕无码不卡啪啪| 厕所偷拍在线| 中文字幕在线观看丝袜| 久久九九视频九九视频| 国产毛片毛片4p懂色| 九九九只有精品| 久久伊人青青草| 蜜臀久久99精品久久久久免费观| 日本污ww视频网站| 成人网站 免费观看| av一区二区三区四区| 96久久久| 视频一区二区免费在线| 伦理第一页| 伊人宅男大香蕉 | 91动漫操逼视频| 亚洲导航深夜福利| 国产高清MV操逼视频| 免费的黄片有限公司| 国产肏逼网站| 亚洲无码成人精品| 国产v片在线免费观看| 国产成人精品必看| www.久久最新地址| 强奸国产在线| 欧美成人黄网色网站| 91动漫操逼视频| 婷婷激情四射| 欧洲综合视频| 亚洲熟女乱熟乱熟妇综合网二区| 97久久精品国产| 久草久日| 免费视频97| 乱伦熟女专区| 欧美日韩国内不卡| 日本一区三级韩国| 99re在线观看| 97在线欧| 黑人猛交| 国产精品久久成人免费| 开心五月激情网| 欧美色66| 久草电影网| 日本中文字幕不卡视频| 国产精品呦一区二区三区| 丝袜大香蕉| 91麻豆天美传媒HD| 欧美操逼录像国产黄色国产| 日韩中文字墓| 国产成人综合网| 丝袜综合| 试看60秒 爽| 强奸乱伦中文字幕AV| 久久久一区二区三区四曲免费听 | 人人看人人摸人人色| 久久久久久久唑| 91红杏| 资源新线在线天堂| 国产精品免费视频不卡| 射欧美综合| 日韩小电影| 静品嫩模一区二区| 超碰97亚洲区| 亚洲视频1区| 男人天堂.AB| 精品九九九九九| 91在线|亚| av天堂电影网| 五月丁香六月激情综合| 久久久青草青青国产亚洲免观精品高清完整版_97久久综合区小说区图片区,国精品 | 少妇久久久久久久久| www.色婷婷| 国产久久天堂资源| 婷婷操逼| 亚洲欧美清纯| 一区二区三区四区色图| 人妻铁牛TV| 爆乳免费黄网站| 国内毛片欧美香蕉精品| 亚洲情色电影网| www国产无码| 日日干夜夜操视频h| 日韩精品国产一区二区| 国产人妻一区二区三区欧美毛片| 三级精品三级在线观看| 人人摸人人干| av网站在线观看了| 超碰欧美97资源| 亚洲中文字幕网| 超碰成人人人爽人人爽| 97se综合网| 国产免费永久精品无码| 麻豆精品.欧美精品.日韩精品.| 亚洲中文字幕97久久精品少妇| 国产午夜精品理论片a大结局| av操操不卡| av资源在线播放天堂| 亚洲性网| 97国产中文| 欧美精品在线观看| 欧美激情中文字幕另类小说| 久久人妻熟女一区二区| 日韩78m视频| 人人天天欧洲| 成人八戒网站| 三级日本一区二区三区| 啊啊在线| 精品妇操一区二区三区| 在现视频女上位好爽| 3028国产精品| 无码精品久久| 舔足天天操天天射| 人伦四五区| 人妻黑丝袜电影| 蜜臀在线免费观看在线免费观看| 色性荡荡荡荡视频| 日本在线不卡v二区| 日本高清视频xxxx| 无码人妻精品酒店| 欧色网址| 玖玖久久久| 97久久久网站| 美女黄色91| 久久久久久无码人妻中文字幕| 91丝袜激情在线| 99国产精品久久久久久久成人热| 亚洲高潮少妇| 色爽爽文学| 成人资源中文字幕在线观看天天| 免费啪啪啪网站18岁| 插日本熟女视频| 欧美综合区| 色婷婷丁香五月| 大香蕉九九| 九九热精品免费视频| 偷拍欧美综合| 亭亭丁香激情| 超碰人妻久久人妻中文97| 欧美一区二区三区大综合| 加勒比AV网| 亚洲一区二区专区-国产丝袜精品丝袜-成人AV| 免费视频在线观看啊啊啊啊啊| 91女网站| 91天射| 青青草原伊人网| 黑人猛交| 日韩中文字幕人妻视频| 在线v中文字幕一区二区三区 | 国产亚洲精品av一区| 日本一区二区三区四区免费观看| 九九九九九九九精品视频| 好一吊区二区| 色色婷婷丁香| 强奸乱伦麻豆| 青草青草久热| 天天色综合图片| 色综合20p| 啪啪啪男女亚洲中文字幕99| 欧美激情高清性猛交| 八戒无码国产午夜福利| av凤凰久久久| 亚洲情色在线| 啊啊啊啊免费视频| 欧美少妇性爱网站| 人妻嗯啊啊在线播放| 天天操天天日天天干| 国产深喉视频一区二区| 一级啊性爱在线视频| 啊嗯好大视频在线观看| 亚洲区小说| 无码在线亚洲| 日日A∨| 张柏芝国产一区在线观看| 怡红院一区二区熟女人妻| 曰本熟女视频| 男人午夜天堂| 亚洲色图超碰在线| www久久精品| 亚洲人妻中文高清| 欧美 日韩 另类 亚洲| 人妻9117c| 亚欧美色图| 欧美gv在线观看| 有码人妻系列| 丁香婷婷五月| 大香蕉伊人在线成人AV在线观看| 91宗合网| 欧美日韩情色一区二区| 日本男人天堂| 91九九九馒头| 在线日韩精品一区二区三区| 曰韩精品九九无码| 白丝AV| 高跟伊人julia ann| 隔壁邻居波多野结衣中文字幕| 秋霞一区二区三区四区五区六区七区| 网站A V在线| 久久一二三四不卡| 操高情无码| 伊人久久AV诱惑悠悠| 九色97| 韩国手机不卡无码三级视频| 久久精品中文| www.99在线| 1769成人国产精品视频| 99re99视频在线免费观看| 天天干夜夜一操| 人妻少妇精品视频一区二区三区| 大香蕉十区| 久久99午夜精品一区人妻| 久久亚洲精品成人av| 国产成人精品必看| 岛国免费黄色网址| 日本性爱少妇| 在线精品福利免费播放| 亚洲欧美色图| 精品无码久久久久久久杏吧| 97 国产精品| 99热自拍| 五月天婷婷激情| 亚洲综合成人网| 国产日韩欧美三级片| 91丝袜美女国产| 精品人人| 精品久久久久瑟瑟| 激情小说五月天| 久久色激情一区二区三区| 六六久久日韩不卡| 亚洲 日韩 欧美 国产综合体| 亚洲综合色男人网| 九九九久千久久激情蜜桃在线看 | 性爱AV天堂| 中文字幕激情小说| 2019精品国产无码成人| 岛国黄| 男人的天堂啪啪| 欧美 亚洲 综合 制服 另类| 天久久久噜噜噜久久国产精品爽爽 | 亚洲欧美日韩精品久| 三级片大波波| 亚洲中文字幕熟女少妇一区二区| 国产亚洲精品农村妇女| 97九色人妻| 成人三级片一区二区三区视频| 夜夜嗨一区| 欧日韩在线观看| 91福利网在线观看| 天天爽天天| 97视频一区| 人人摸人人干人人拍97| 四虎免费看黄| 久久这里只精品免费福利| 亚洲图片欧美日韩| 九九九九九九九九九国产精品| 人人干人人搞人人摸| 啊啊啊啊啊啊啊啊要喷了| 天天弄天天操| 亚洲天堂第一页| 黄人人操人人操| 色婷婷久久综合超碰| 黑人白女精品一区| 99re8免费高清在线| 岛国黄片网站| 91成人国产综合久久精品蜜月| 在线播放成人高清免费视频| 日本免费一区二| 国产无码高清操逼视频|