【總結(jié)】活用 async/await ,實(shí)現(xiàn)一些讓Vue更好用的裝飾器

Async/await加上裝飾器,會(huì)產(chǎn)生神奇的效果。
以下所列幾個(gè)裝飾器,都要求被裝飾的方法寫成async/await,
這樣就可以利用async/await本身的特性,從方法外部得知異步方法是否運(yùn)行完成。
于是便實(shí)現(xiàn)了將繁雜和麻煩的邏輯隱藏在裝飾器內(nèi)部,使業(yè)務(wù)代碼更加干凈。
以下裝飾器除了最后一個(gè)都是用在Typescript環(huán)境下的class寫法的vue里的。
最后一個(gè)其實(shí)并不是裝飾器,而是一個(gè)高階函數(shù),但是眾所周知,裝飾器本質(zhì)上就是個(gè)高階函數(shù)。
所以所有裝飾器都可以很容易的改成高階函數(shù),然后用在js環(huán)境的vue里。
給vue添加一個(gè)指示初始化完成的變量。
使用場(chǎng)景舉例:
搜索頁(yè)面:搜索中顯示loading,結(jié)果為空時(shí)顯示暫無(wú)數(shù)據(jù)。
第一次打開頁(yè)面時(shí),在created或者mounted中發(fā)出請(qǐng)求,進(jìn)行初始化的搜索,此時(shí)搜索還沒(méi)完成,顯示暫無(wú)數(shù)據(jù)并不合適。
這個(gè)時(shí)候就需要一個(gè)變量來(lái)判斷頁(yè)面是不是第一次打開。代碼解釋:
通過(guò)裝飾器添加這個(gè)屬性(pageIsReady)到組件的類上,
并包裝vue的created, mounted和beforeDestroy方法,監(jiān)控組件的生命周期。
當(dāng)created或者mounted里發(fā)出的請(qǐng)求完成后,就把pageIsReady設(shè)為true。
然后在beforeDestroy中重置狀態(tài),因?yàn)檠b飾器用了閉包,只會(huì)實(shí)例化一次。
import { Constructor } from "vue/types/options";
export type WrapReadyProperty<T> = T & {
pageIsReady?: boolean;
createdDone?: boolean;
mountedDone?: boolean
}
/**
* 在@compontent 之后使用這個(gè)裝飾器,
* 組件就會(huì)被注入屬性 pageIsReady,
* 當(dāng)created和mounted都執(zhí)行完成時(shí) pageIsReady 變成true,
* 要求mounted或created是async/await。(取決于在哪個(gè)方法中發(fā)請(qǐng)求初始化組件)
* 然后可以在template中直接使用。
* 在script中使用調(diào)用isPageReady.call(this)方法;
*/
export default function PageReadyStatus() {
return function pageReadyEnhancement<T extends WrapReadyProperty<Constructor>>(target: T) {
const oldMounted = target.prototype.mounted || function() { }
const oldCreated = target.prototype.created || function() { }
const oldBeforeDestroy = target.prototype.beforeDestroy || function() { }
target.prototype.pageIsReady = false;
function isCreatedMountedAllDone(this: T) {
return !!this.createdDone && !!this.mountedDone;
}
target.prototype.created = async function(...params: any[]) {
await oldCreated.apply(this, params);
this.createdDone = true;
this.pageIsReady = isCreatedMountedAllDone.call(this)
}
target.prototype.mounted = async function(...params: any[]) {
await oldMounted.apply(this, params);
this.mountedDone = true
this.pageIsReady = isCreatedMountedAllDone.call(this)
}
target.prototype.beforeDestroy = async function(...params: any[]) {
await oldBeforeDestroy.apply(this, params);
this.createdDone = false;
this.mountedDone = true
this.pageIsReady = false;
}
return target
};
}
export function isPageReady(this: WrapReadyProperty<Vue>) {
return this.pageIsReady
}
給事件回調(diào)函數(shù)和按鈕Dom添加防抖與loading樣式
使用場(chǎng)景舉例:
點(diǎn)擊一個(gè)按鈕,觸發(fā)一個(gè)函數(shù),并發(fā)出一個(gè)請(qǐng)求。此時(shí)需要給這個(gè)函數(shù)防抖,并給按鈕增加一個(gè)loading的樣式
代碼解釋
利用async/await把異步變成同步的特性,在裝飾器中發(fā)現(xiàn)方法還沒(méi)執(zhí)行完成時(shí)直接返回。
并從時(shí)間對(duì)象中拿到按鈕的dom節(jié)點(diǎn),改變按鈕的樣式。我這里是用了一個(gè)cursor:wait;
/*
* 請(qǐng)保證被包裝的方法的參數(shù)列表最后一個(gè)是點(diǎn)擊事件的參數(shù)
*/
export default function buttonThrottle() {
let pending = false;
return function(target: any, name: string): any {
const btnClickFunc = target[name];
const newFunc = async function(this: Vue, ...params: any[]) {
if (pending) {
return;
}
const event:Event = params[params.length - 1];
let btn = event.target as HTMLElement
pending = true;
const recoverCursor = changeCursor(btn);
try {
await btnClickFunc.apply(this, params);
} catch (error) {
console.error(error);
}
recoverCursor();
pending = false;
};
target[name] = newFunc;
return target;
};
}
function changeCursor(btn?: HTMLElement) {
if (btn == null) {
return () => {};
}
const oldCursor = btn.style.cursor;
btn.style.cursor = "wait";
return () => {
btn.style.cursor = oldCursor;
};
}
用法:
在點(diǎn)擊事件函數(shù)上使用這個(gè)裝飾器.
import { Component, Vue } from "vue-property-decorator";
import buttonThrottle from "@/ui/view/utils/buttonThrottle";
type Member = { account_no: string; name: string; warn?: string };
@Component({ components: {} })
export default class AddMemberInput extends Vue {
@buttonThrottle()
private async confirmAdd() {
await this.addMembers(this.getVaildMembers());
}
}
注入loading變量
場(chǎng)景
發(fā)請(qǐng)求時(shí)在頁(yè)面上加一個(gè)loading的狀態(tài),比如element-ui里就有一個(gè)v-loading的指令。
這個(gè)指令需要一個(gè)是否在loading中的變量
代碼解釋
往組件上增加一個(gè)變量,變量名由參數(shù)variableName指定
該變量會(huì)在被包裝的方法執(zhí)行期間為true
這樣就不用自己寫this.loading=true this.loading=false了
export default function FunctionLoadingVariable(variableName: string) {
return function(target: any, name: string): any {
target[variableName] = false;
const btnClickFunc = target[name];
const newFunc = async function(this: Vue & {
[key: string]: boolean
}, ...params: any[]) {
try {
this[variableName] = true
await btnClickFunc.apply(this, params);
} catch (error) {
console.error(error);
}
this[variableName] = false
};
target[name] = newFunc;
return target;
};
}
mounted之前顯示白屏
場(chǎng)景
頁(yè)面加載的過(guò)程很丑,所以可以在mouted之前顯示白屏,或者弄一個(gè)骨架屏。
特別是在微信小程序中,因?yàn)榧虞d完成之前特別丑,所以幾乎所有小程序都會(huì)在mounted之前白屏。
通過(guò)async/await獲得mounted或者created是否執(zhí)行完成
再通過(guò)指向vue實(shí)力的this拿到組件根節(jié)點(diǎn),然后按需修改它
以下代碼只是將組件隱藏了,實(shí)際上可以寫更復(fù)雜的邏輯,在加載過(guò)程中顯示其他內(nèi)容,畢竟拿到了Dom,想干嘛就干嘛。
function firstPaintControl(vueObj) {
let oldMounted = vueObj.mounted || function() {};
vueObj.mounted = async function(...params) {
this.$el.style.visibility = 'hidden';
await oldMounted.apply(this, params);
this.$el.style.visibility = 'visible';
};
return vueObj;
}
關(guān)于本文
來(lái)源:OLong
https://segmentfault.com/a/1190000037604556
