最近有一个这样的需求:
点赞功能:点了赞之后要先亮起来,然后再去请求后端的接口,1、成功了不变!2、失败了要回滚到之前的状态。之前一直有听说后端有事务回滚(没去了解过实现原理,但应该就是出问题了,回到上一个状态吧!)
实现的代码如下:
useTransaction.ts
interface UseTransactionParams<T> {
initialValue?: T[];
maxLength?: number;
// 回滚回调方法
rollbackCallback?: (item: T) => void;
}
interface UseTransactionReturn<T> {
commit: (item: T) => void;
rollback: () => void;
getLast: () => T;
}
/**
* 事务回滚
* 【业务场景】点赞
* 【流程】先亮,若失败则回滚
*/
export function useTransaction<T>(
params?: UseTransactionParams<T>
): UseTransactionReturn<T> {
let list: T[] = [];
const initialValue = params?.initialValue || [];
const maxLength = Math.abs(params?.maxLength || 2);
const rollbackCallback = params?.rollbackCallback;
if (initialValue.length) {
list.push(...initialValue);
}
const updateList = () => {
list = list.slice(-maxLength);
};
updateList();
const getLast = () => {
return list[list.length - 1];
};
const commit = (item: T) => {
list.push(item);
updateList();
};
const rollback = () => {
rollbackCallback && rollbackCallback(getLast());
};
return {
// 提交事务
commit,
// 回滚
rollback,
// 获得最近的数据
getLast,
};
}
使用:
<template>
<LikeAndUnlike
:like="like"
:unlike="unlike"
:loading="loading"
@like="handleLike"
@unlike="handleUnLike"
@update="handleUpdateStatusDebounce"
/>
</template>
<script lang="ts" setup>
import LikeAndUnlike from "./LikeAndUnlike.vue";
import type {
FeedBackCreatePopupParams,
GetChatListApiItemDTO,
} from "@/api/chat/types";
import { ref, computed } from "vue";
import { debounce } from "lodash-es";
import { showToast } from "vant";
import { useTransaction } from "@/hooks/web/useTransaction";
const props = defineProps<{
item: GetChatListApiItemDTO;
isLastMsg?: boolean;
}>();
type Loop = () => void;
const emit = defineEmits<{
(
event: "delete-feed-back",
item: GetChatListApiItemDTO,
done: Loop,
success: Loop,
fail: Loop
): void;
(
event: "update-feed-back",
item: GetChatListApiItemDTO,
params: FeedBackCreatePopupParams,
done: Loop,
success: Loop,
fail: Loop
): void;
}>();
const loading = ref(false);
const like = ref<boolean>(props.item.feedCode === "1");
const unlike = ref<boolean>(props.item.feedCode === "0");
const likeStatus = computed(() => {
if (like.value) {
return "1"; // 喜欢
}
if (unlike.value) {
return "0"; // 不喜欢
}
return "delete"; // 两个都没选
});
const handleLike = (value: boolean) => {
like.value = value;
};
const handleUnLike = (value: boolean) => {
unlike.value = value;
};
const transaction = useTransaction<[boolean, boolean]>({
// 初始事务
initialValue: [[like.value, unlike.value]],
// 回滚事务时触发的方法
rollbackCallback([prevLike, prevUnLike]) {
like.value = prevLike;
unlike.value = prevUnLike;
},
});
/**
* 校验值的必要性
* fixbug: 来回切换触发重复 删除/创建
*/
const checkNecessity = (nowLike: boolean, nowUnLike: boolean) => {
const [prevLike, preUnLike] = transaction.getLast();
if (prevLike === nowLike && preUnLike === nowUnLike) {
throw Error(`没必要的提交,请求的新值和旧值相同!`);
}
};
const handleUpdateStatus = (
params: Partial<FeedBackCreatePopupParams>,
needCheck = true
) => {
needCheck && checkNecessity(like.value, unlike.value);
loading.value = true;
const feedCode = likeStatus.value;
const done = () => (loading.value = false);
const success = () => {
transaction.commit([like.value, unlike.value]); // 推送事务
};
const fail = () => {
showToast("操作失败");
transaction.rollback(); // 事务回滚
};
if (feedCode === "delete") {
emit("delete-feed-back", props.item, done, success, fail);
return;
}
params.feedCode = feedCode;
const newSuccess = () => {
success();
if (params.feedCode === "0" && (params.feedType || params.feedContent)) {
// 如果是踩 且 填写了踩的内容
showToast("反馈成功");
}
};
emit(
"update-feed-back",
props.item,
params as FeedBackCreatePopupParams,
done,
newSuccess,
fail
);
};
/**
* 防止用户疯狂 取消/确定
*/
const handleUpdateStatusDebounce = debounce(handleUpdateStatus, 500);
</script>
核心就是:初始化时记录回滚方法。然后成功时记录这次成功的值,如果失败就执行回滚,那就会去执行我们初始化的方法并传入上一次成功的值,用于回滚!