머리말
로우코드 프로젝트에서 마주치는 실행 취소 및 다시 실행 기능은 vue3을 기반으로 구현됩니다.
구현 단계
- pinia를 사용하여 기록 레코드 배열(arr), 현재 페이지에 표시되는 해당 인덱스(index) 및 지금 스냅샷 생성 여부(isSnapshot)를 저장합니다. 시간은 시계에서 메서드를 트리거하고 데이터의 최대 수를 트리거합니다. 저장할 수 있습니다(maxStep). 여러 방법이 pinia에 정의되어 있습니다.
- 첫 번째는 스냅샷을 생성하고, 페이지 요소 배열의 변경 사항을 모니터링하고, 변경될 때 메서드를 호출하고, 메서드를 입력한 후 스냅샷을 생성할 수 있는지 여부를 판단합니다. isSnapshot=true로 종료할 수 없습니다. 먼저 색인이 대기열 끝에 있는지 여부를 판단합니다. 더 이상 색인 뒤의 요소를 삭제할 필요가 없으며 페이지 요소 배열을 딥 복사하여 기록 레코드 배열로 푸시합니다.
addSnapshot() {
if (this.isSnapshot) {
this.isFull();//判断是否溢出 溢出删除一个
let n = this.snapshotData.length;
// 当前索引不在开头需要恢复时
if (this.curIndex < n-1) {
this.snapshotData.splice(this.curIndex+1);
}
// elStore().els为页面元素数组
this.snapshotData.push(JSON.stringify(elStore().els));
this.curIndex++;
}
this.isSnapshot = true;
}
- 두 번째는 오버플로 여부를 판단하고 오버플로는 요소를 이동합니다.
// 判断是否溢出
isFull() {
if (this.snapshotData.length == this.maxStep) {
this.snapshotData.shift(0);
}
},
- 세 번째 취소는 index>0인 경우 지금 취소할 수 있음을 증명한 다음 isSnapshot=false를 설정한 다음 인덱스를 1씩 감소시키고 인덱스가 가리키는 요소 배열을 페이지에 렌더링합니다.
// undo撤销
undo() {
if (this.curIndex > 0) {
this.isSnapshot = false;
let t = JSON.parse(this.snapshotData[--this.curIndex]);
for (let i = 0; i < t.length; i++) {
elStore().els[i] = t[i];
}
}
},
- 네 번째 redo는 배열의 끝이 redo 가능하지 않은 경우 isSnapshot=false로 설정한 다음 인덱스에 1을 추가하고 인덱스가 가리키는 요소 배열을 페이지에 렌더링합니다.
// record 恢复
record() {
// 判断是不是到尾部,有才可以恢复
if (this.curIndex < this.snapshotData.length-1) {
this.isSnapshot = false;
let t = JSON.parse(this.snapshotData[++this.curIndex]);
for (let i = 0; i < t.length; i++) {
elStore().els[i] = t[i];
}
}
}
주의점
간단하고 편리하지만 데이터 손실이 발생할 수 있습니다.
결점:
- JSON.Stringify로 변환된 데이터에 function, undefined 및 Symbol과 같은 열거 불가능한 속성이 포함되어 있으면 JSON.Stringify 직렬화 후에 키-값 쌍이 사라집니다.
- 변환된 데이터에는 NaN 및 Infinity 값(-Infinity 포함)이 포함되며 JSON 직렬화 이후의 결과는 null이 됩니다.
- 변환된 데이터에는 JSON.Stringify 직렬화 후 문자열이 되는 Date 개체가 포함되어 있습니다.
- RegExp 참조 유형을 포함하는 변환된 데이터는 직렬화 후에 빈 개체가 됩니다.
- 열거 불가능한 속성을 직렬화할 수 없습니다.
- 개체에 대한 순환 참조는 직렬화할 수 없습니다(예: obj[key] = obj).
- 개체의 프로토타입 체인을 직렬화할 수 없습니다.
전체 코드
// 监听事件,这里会遇到频繁触动的问题,用防抖解决
watch(useElStore.els, debounce(() => {
snapshotStore.addSnapshot();
}, 400), {
immediate: true });
// 撤销
function cancel() {
changeRectShow();
snapshotStore.undo();
}
// 重做
function record() {
changeRectShow();
snapshotStore.record();
}
// 防抖代码
export default (fn,delay) =>{
let t = null;
return () => {
if (t != null) {
clearInterval(t);
}
t = setTimeout(() => {
fn();
},delay)
}
}
// snapshot仓库
import {
defineStore } from 'pinia'
import elStore from './index'
export default defineStore('snapshot', {
state() {
return {
snapshotData: [],// 快照数据
maxStep: 30,// 最大能够存储的数据数
curIndex: -1,// 当前所在下标
isSnapshot: true,// 是否可以保存
}
},
actions: {
// 添加快照
addSnapshot() {
if (this.isSnapshot) {
this.isFull();//判断是否溢出 溢出删除一个
let n = this.snapshotData.length;
// 当前索引不在尾部,说明进行了撤销,需要删除后面的元素
if (this.curIndex < n-1) {
this.snapshotData.splice(this.curIndex+1);
}
this.snapshotData.push(JSON.stringify(elStore().els));
this.curIndex++;
}
this.isSnapshot = true;
},
// 判断是否溢出
isFull() {
if (this.snapshotData.length == this.maxStep) {
this.snapshotData.shift(0);
}
},
// undo撤销
undo() {
if (this.curIndex > 0) {
this.isSnapshot = false;
let t = JSON.parse(this.snapshotData[--this.curIndex]);
for (let i = 0; i < t.length; i++) {
elStore().els[i] = t[i];
}
}
},
// record 恢复
record() {
// 判断是不是到尾部,有才可以恢复
if (this.curIndex < this.snapshotData.length-1) {
this.isSnapshot = false;
let t = JSON.parse(this.snapshotData[++this.curIndex]);
for (let i = 0; i < t.length; i++) {
elStore().els[i] = t[i];
}
}
}
}
})