活用 async-await,讓 Vue 變得更好用的裝飾器!

作者:OLong

https://segmentfault.com/a/1190000037604556

下文三個裝飾器,都是利用了 async/await 把異步變成同步的特性實現的。

要求被裝飾的方法必須寫成 async/await,用起來十分方便,實現徹底被隱藏在了裝飾器內部。

前兩個都是用在 ts 環境下 class 寫法的 vue 裏的。不過看清楚邏輯後,很容易修改成可以用在 js 環境中的 vue 組件上。

  1. 給 vue 添加一個指示初始化完成的變量。

指業務相關的初始化邏輯都完成了 比如搜索功能:搜索中顯示 loading,結果爲空時顯示暫無數據。但是第一次打開頁面時,搜索還沒完成,但顯示暫無數據又不合適 這個時候就需要一個這樣的變量處理邊界情況 用於 ts 環境下的 vue

通過裝飾器添加這個屬性,幷包裝 vue 的 created, mounted 和 beforeDestroy 方法。當 created 或者 mounted 裏發出的請求完成後,就把 pageIsReady 設爲 true。使用這個裝飾器時,在業務代碼中完全無感,沒有任何心智負擔。

import { Constructor } from"vue/types/options";
export type WrapReadyProperty<T> = T & { pageIsReady?: boolean }
/**
 * 在@compontent 之後使用這個裝飾器,
 * 組件就會被注入屬性 pageIsReady,
 * 當created和mounted都執行完成時 pageIsReady 變成true,
 * 要求mounted或created是async/await。(取決於在哪個方法中發請求初始化組件)
 * 然後可以在template中直接使用。
 * 在script中使用調用isPageReady.call(this)方法;
    */
exportdefaultfunction PageReadyStatus() {
    let createdDone = false;
    let mountedDone = false;
    function isCreatedMountedAllDone() {
        return createdDone && mountedDone;
    }
    returnfunction pageReadyEnhancement<T extends 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;
        target.prototype.created = asyncfunction(...params: any[]) {
            await oldCreated.apply(this, params);
            createdDone = true;
            this.pageIsReady = isCreatedMountedAllDone()
        }
        target.prototype.mounted = asyncfunction(...params: any[]) {
            await oldMounted.apply(this, params);
            mountedDone = true;
            this.pageIsReady = isCreatedMountedAllDone()
        }
        target.prototype.beforeDestroy = asyncfunction(...params: any[]) {
            await oldBeforeDestroy.apply(this, params);
            mountedDone = false;
            createdDone = false;
            this.pageIsReady = false;
        }
        return target
    };
}

exportfunction isPageReady(this: WrapReadyProperty<Vue>) {
    returnthis.pageIsReady
}
  1. 給事件回調函數和按鈕 Dom 添加防抖與 loading 樣式

用於 ts 環境下的 vue

通過裝飾器包裝被裝飾的方法。要求被包裝的方式是 async/await 的。這樣裝飾器內只需要用一個 await 就可以得知被包裝的方法是否執行完成。同時,可以從事件對象中拿到被點擊的 dom 元素並修改它。

/*
 * 請保證被包裝的方法的參數列表最後一個是點擊事件的參數
 */
exportdefaultfunction buttonThrottle() {
    let pending = false;
    returnfunction(target: any, name: string): any {
        const btnClickFunc = target[name];
        const newFunc = asyncfunction(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;
    };
}

用法: 在點擊事件函數上使用這個裝飾器。裝飾器會自動檢測該函數是否執行完成,並在執行過程中往按鈕的 Dom 節點上添加 point:wait 屬性

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: {} })
    exportdefaultclass AddMemberInput extends Vue {        @buttonThrottle()
        private async confirmAdd() {
            awaitthis.addMembers(this.getVaildMembers());
        }
    }
  1. mounted 之前顯示白屏

用於 js 的 vue 中包裝 vue 的對象

同上,通過 async/await 獲得 mounted 或者 created 是否執行完成 再通過指向 vue 實力的 this 拿到組件根節點,然後按需修改它 以下代碼只是將組件隱藏了,實際上可以寫更復雜的邏輯,在加載過程中顯示其他內容,畢竟拿到了 Dom,想幹嘛就幹嘛。

function firstPaintControl(vueObj) {
    let oldMounted = vueObj.mounted || function() {};
    vueObj.mounted = asyncfunction(...params) {
      this.$el.style.visibility = 'hidden';
      await oldMounted.apply(this, params);
      this.$el.style.visibility = 'visible';
    };
    return vueObj;
  }
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/c8apwepC0walXTrexBlfOg