Sam*_* B. 5 autosave typescript vue.js vuejs3 pinia
我有一个 Vue3 应用程序,我想传达一种特定类型的 UX,其中对资源所做的更改会自动保存(即没有保存按钮)。该应用程序有几种不同类型的可编辑资源和几个不同的编辑器组件,我想创建一个可以处理自动保存并且可以简单地插入我的编辑器组件内部的抽象。作为附加要求,某些字段必须去抖(例如文本字段),而其他字段则要立即保存到服务器。
我有兴趣了解使用我在这里提供的解决方案可能存在哪些缺点以及是否有更好的方法。
想法:
AutoSaveManager<T>来处理类型对象的自动保存T。类的代码:
/* eslint-disable @typescript-eslint/no-explicit-any */
import { debounce, DebouncedFunc } from "lodash";
type RemotePatchFunction<T> = (changes: Partial<T>) => Promise<void>;
type PatchFunction<T> = (changes: Partial<T>, reverting?: boolean) => void;
export type FieldList<T> = (keyof T)[];
enum AutoSaveManagerState {
UP_TO_DATE,
PENDING,
ERROR,
}
export class AutoSaveManager<T> {
instance: T;
unsavedChanges: Partial<T>;
beforeChanges: Partial<T>;
remotePatchFunction: DebouncedFunc<RemotePatchFunction<T>>;
localPatchFunction: PatchFunction<T>;
errorFunction?: (e: any) => void;
successFunction?: () => void;
cleanupFunction?: () => void;
debouncedFields: FieldList<T>;
revertOnFailure: boolean;
alwaysPatchLocal: boolean;
state: AutoSaveManagerState;
constructor(
instance: T,
remotePatchFunction: RemotePatchFunction<T>,
localPatchFunction: PatchFunction<T>,
debouncedFields: FieldList<T>,
debounceTime: number,
successFunction?: () => void,
errorFunction?: (e: any) => void,
cleanupFunction?: () => void,
revertOnFailure = false,
alwaysPatchLocal = true,
) {
this.instance = instance;
this.localPatchFunction = localPatchFunction;
this.remotePatchFunction = debounce(
this.wrapRemotePatchFunction(remotePatchFunction),
debounceTime,
);
this.debouncedFields = debouncedFields;
this.unsavedChanges = {};
this.beforeChanges = {};
this.successFunction = successFunction;
this.errorFunction = errorFunction;
this.cleanupFunction = cleanupFunction;
this.revertOnFailure = revertOnFailure;
this.alwaysPatchLocal = alwaysPatchLocal;
this.state = AutoSaveManagerState.UP_TO_DATE;
}
async onChange(changes: Partial<T>): Promise<void> {
this.state = AutoSaveManagerState.PENDING;
// record new change to field
this.unsavedChanges = { ...this.unsavedChanges, ...changes };
// make deep copy of fields about to change in case rollback becomes necessary
// (only for non-debounced fields as it would be disconcerting to roll back
// debounced changes like in text fields)
Object.keys(changes)
.filter(k => !this.debouncedFields.includes(k as keyof T))
.forEach(
k => (this.beforeChanges[k] = JSON.parse(JSON.stringify(this.instance[k]))),
);
if (this.alwaysPatchLocal) {
// instantly update in-memory instance
this.localPatchFunction(changes);
}
// dispatch update to backend
await this.remotePatchFunction(this.unsavedChanges);
if (Object.keys(changes).some(k => !this.debouncedFields.includes(k as keyof T))) {
// at least one field isn't to be debounced; call remote update immediately
await this.remotePatchFunction.flush();
}
}
private wrapRemotePatchFunction(
callback: RemotePatchFunction<T>,
): RemotePatchFunction<T> {
/**
* Wraps the callback into a function that awaits the callback first, and
* if it is successful, then empties the unsaved changes object
*/
return async (changes: Partial<T>) => {
try {
await callback(changes);
if (!this.alwaysPatchLocal) {
// update in-memory instance
this.localPatchFunction(changes);
}
// reset bookkeeping about recent changes
this.unsavedChanges = {};
this.beforeChanges = {};
this.state = AutoSaveManagerState.UP_TO_DATE;
// call user-supplied success callback
this.successFunction?.();
} catch (e) {
// call user-supplied error callback
this.errorFunction?.(e);
if (this.revertOnFailure) {
// roll back unsaved changes
this.localPatchFunction(this.beforeChanges, true);
}
this.state = AutoSaveManagerState.ERROR;
} finally {
this.cleanupFunction?.();
}
};
}
}
Run Code Online (Sandbox Code Playgroud)
此类将在编辑器组件内部实例化,如下所示:
this.autoSaveManager = new AutoSaveManager<MyEditableObject>(
this.modelValue,
async changes => {
await this.store.someAction({ // makes an API call to the server to actually update the object
id: this.modelValue.id
changes,
});
this.saving = false;
},
changes => {
this.saving = true;
this.savingError = false;
this.store.someAction({ // only updates the local object in the store
id: this.modelValue.id
changes;
})
},
["body", "title"], // these are text fields and need to be debounced
2500, // debounce time
undefined, // no cleanup function
() => { // function to call in case of error
this.saving = false;
this.savingError = true;
},
);
Run Code Online (Sandbox Code Playgroud)
然后,在模板内部,您可以像这样使用它:
<TextInput
:modelValue="modelValue.title"
@update:modelValue="autoSaveManager.onChange({ title: $event })"
/>
Run Code Online (Sandbox Code Playgroud)
我可以做些什么来改进这个想法吗?我应该使用完全不同的方法来在 Vue 中自动保存吗?
| 归档时间: |
|
| 查看次数: |
499 次 |
| 最近记录: |