vue2中使用ts,更健全的類型推斷

vue中的使用場景

tsconfig注意事項

注意你須要引入 strict: true (或者至少 noImplicitThis: true,這是 strict 模式的一部分) 以利用組件方法中 this 的類型檢查,不然它會始終被看做 any 類型。

須要注意如下的配置項,否則類型推斷不能正常運做vue

// 啓用全部嚴格類型檢查選項
"strict": true,
// 禁止this 值爲 any
"noImplicitThis": true,

Vue.extend()的做用

vue.extend方法已經聲明瞭參數類型,因此vue.extend能夠爲你提供類型推斷。

舉個例子,看下面一段代碼:
ios

type vueExtend = (option: { name: string, $store: object, $route: object }) => void;
const vueInstance: vueExtend = ({ $ }) {};

vue原型混入屬性或方法

平時咱們寫的 $store 或 $route 等方法插件向vue原型中添加了原型屬性$store和$route。git

那如何在ts中的vue原型中混入這些屬性或方法呢?github

從vuex導出的ts聲明文件,咱們能夠看到下代碼:ajax

import Vue, { ComponentOptions } from "vue";
import { Store } from "./index";

declare module "vue/types/options" {
  interface ComponentOptions<V extends Vue> {
    store?: Store<any>;
  }
}

declare module "vue/types/vue" {
  interface Vue {
    $store: Store<any>;
  }
}

因此若是咱們想混入$api的話也是須要這麼作的。vuex

先去新建一個global.d.ts,使用如下的代碼axios

declare module "vue/types/vue" {
  interface Vue {
    $api: {
      queryHotel: <T>(options: any) => Promise<T>;
    };
  }
 
// 使用
this.$api.queryHotel(payload).then(() => {});

推斷vue組件實例類型

爲何須要推斷vue實例類型,這是有使用場景的。segmentfault

好比咱們須要拿到子組件的引用,並調用子組件的方法的時候。api

假設此時子組件爲SpaceDrawer, ref爲spaceDrawer。函數

// 同步移除物聯樹的節點
(this.$refs.spaceDrawer as any).removeCheckNode(delRoomNode[0]);

獲取SpaceDrawer實例類型
type SpaceDrawerRef = InstanceType<typeof SpaceDrawer>;

// 同步移除物聯樹的節點
(this.$refs.spaceDrawer as SpaceDrawerRef).removeCheckNode(delRoomNode[0]);

採用高級類型InstanceType可取得vue組件實例SpaceDrawer。

vue中從新聲明data中屬性的類型

vue中的類型得益於vue.extend能自動推斷類型,可是有時候你須要自定義類型。以下例子

export default Vue.extend({
  data() {
      return {
        obj: {
          name: '',
          value: ''
        }
      }
  },
  methods: {
    handleObj(type: string) {
     // 這樣是能夠的
      this.obj.name = 'xxx';
     // 這樣是不行的,(好比type就是name)
      this.obj[type] = 'xxx' 
    }
  }
})

如上兩種狀況,第二種顯然須要從新定義類型,由於這須要用可索引類型了。

咱們能夠對data內的字段從新定義類型,也就是類型斷言。
以下:

type ObjType = { [key: string]: string; name: string; value: string }
export default Vue.extend({
  data() {
      return {
        obj: {
          name: '',
          value: ''
        } as ObjType,
      }
  },
  methods: {
    handleObj(type: string) {
     // 這樣是能夠的
      this.obj[type] = 'xxx' 
    }
  }
})

vue中從新聲明props的類型

跟data是差很少,只是vue的類型系統已經爲咱們提供了斷言的類型 - PropType

用法以下:

export default Vue.extend({
    props: {
        spaceModel: {
            type: Number as PropType<SpaceModelEnum>,
            default: SpaceModelEnum.All,
        },
        spaceOperator: {
            type: String as PropType<SpaceOperator>,
            default: SpaceOperator.CREATE,
        }
    },
}

computed中get,set使類型推斷異常

這裏須要注意一下,有時候會遇到類型推斷不起做用了,而且computed中存在異常。
這是由於computed的異常阻斷了類型的進一步推斷。

以下狀況,此時已經不能推斷出prop裏面的屬性:

解決方法就是,對這種寫法的類型申明補全。

讓mixins獲得類型推斷

讓mixins獲得類型推斷

常規在組件中寫入mixins屬性,mixins中的組件類型是得不到推斷的。

例子,這是一個mixins組件

<script lang="ts">
import Vue from 'vue';




export default Vue.extend({
    created() {
        console.log('mixin created');
    },
    methods: {
        ok() {
            console.log('this.isOk');
        },
        test(): boolean {
            return true;
        },
    },
});
</script>

接下來,在組件中這麼引用。

import Vue from 'vue';
import MixinTets from './mixinTest.vue';




export default Vue.extend({
    name: 'TagList',
    components: {},
    props: {
        tagList: {
            type: Array,
            required: true,
        },
    },
    mixins: \[MixinTets\],
    data() {
        return {};
    },
    methods: {
        handleClickTag(item: any) {
            this.t
            this.$emit('handleClickTag', item);
        },
    },
})

咱們發現並不能正確的推導組件的實例(這裏應該有test和ok方法)。

那咱們怎麼解決這樣的問題呢?

首先,咱們須要提供一個類型推斷,這裏沒看懂不要緊,他只是對vue實例中的mixins方法作了類型推論。

  1. 推斷傳入單個vue實例
  2. 推斷傳入多個vue實例,作下類型交叉。
/\* eslint-disable max-len, import/export, no-use-before-define \*/
import Vue, { VueConstructor } from 'vue';




export default function mixins<T extends VueConstructor\[\]>(
    ...args: T
): ExtractVue<T> extends infer V ? (V extends Vue ? VueConstructor<V> : never) : never;
export default function mixins<T extends Vue>(...args: VueConstructor\[\]): VueConstructor<T>;
export default function mixins(...args: VueConstructor\[\]): VueConstructor {
    return Vue.extend({ mixins: args });
}




/\*\*
 \* Returns the instance type from a VueConstructor
 \* Useful for adding types when using mixins().extend()
 \*/
export type ExtractVue<T extends VueConstructor | VueConstructor\[\]> = T extends (infer U)\[\]
    ? UnionToIntersection<U extends VueConstructor<infer V> ? V : never>
    : T extends VueConstructor<infer V>
    ? V
    : never;




type UnionToIntersection<U> = (U extends any
  ? (k: U) => void
  : never) extends (k: infer I) => void
    ? I
    : nev

根據以上的代碼能夠寫出以下結構,可得類型推斷。

import Mixins from 'utils/mixin';
import MixinTets from './mixinTest.vue';




export default Mixins(MixinTets).extend({
    name: 'TagList',
    components: {},
    props: {
        tagList: {
            type: Array,
            required: true,
        },
    },
    data() {
        return {};
    },
    methods: {
        handleClickTag(item: any) {
            this.$emit('handleClickTag', item);
        },
    },
})

如何寫多個mixin?

枚舉讓代碼更清晰

相信你們都知道,枚舉有兩種。

  • 一種是常量枚舉
  • 一種是普通枚舉
// 一、常量枚舉
const enum State { SUCCESS = 0, FAIL }
// 二、枚舉
enum State { SUCCESS = 0, FAIL }

那何時用常量枚舉,何時用普通枚舉。

你只須要知道常量枚舉在js運行時是ts編譯後的值。

什麼意思?

若是你把常量枚舉當成一個常量是不行的,由於它並不存在。

enum SpaceModelEnum {
    All = 0,
    ONLY_BUILDING_ROOM,
    ONLY_FLOOR_ROOM,
    ONLY_ROOM,
}
const enum SpaceType {
    BUILDING = 0,
    FLOOR,
    ROOM,
}

export default Vue.extend({
    name: 'SpaceCreate',
    data() {
        return {
            // 能夠 
            SpaceModelEnum,
            // 不行
            SpaceType
        };
    },
})

枚舉用處很大,能夠大大提升代碼的可讀性,固然用map也是能接受的。

switch (level) {
    case SpaceType.BUILDING:
        break;
    case SpaceType.FLOOR:
        parentNode = spaceDestruct.genBuildingNode();
        spaceTree.push(parentNode);
        break;
    case SpaceType.ROOM:
        const buildingNode = spaceDestruct.genAll();
        parentNode = buildingNode.children[0];
        spaceTree.push(buildingNode);
    default:
        break;
}

這裏能夠很清楚的知道level多是building,floor,room類型。而不是0,1,2這種可讀性很低的代碼。

ts常規用法

經常使用的ts高級類型

  • Record - 常常用的,便捷定義可索引類型,Record<string, { name: string, value: string }>
  • ReturnType - 得到函數的返回類型
  • ConstructParameter - 獲取構造函數參數類型
  • typeof - 取得對象的類型等
  • keyof - 獲取接口類型的key,組合爲聯合類型
  • in - 枚舉聯合類型

一個優秀的ts高級類型開源庫:
ts-toolbelt

一篇關於高級類型的博文:
【速查手冊】TypeScript 高級類型 cheat sheet

使用泛型動態推斷api接口返回的數據類型

不一樣的請求接口,返回不一樣的數據類型,那麼咱們如何可以定義這種類型呢?咱們很容易想到用泛型。

可是每一個接口都會返回固定的格式:

{
    code: number;
    msg: string | null;
    result: any;
}

那麼咱們第一步須要把通用的格式抽出來,接下來result就是經過泛型去動態定義了。
這是我封裝後的例子。

type ResType<T> = {
    code: number;
    msg: string | null;
    result: T;
};

api<T>(urlProp: string, params: Record<string, any>, config = {}): Promise<ResType<T>> {
    const { get: urlGet, post: urlPost } = this.urlObject;
    let url = urlGet[urlProp];


    if (url && url.length > 0) {
        return ajax.get<T>(url, params, config);
    }


    url = urlPost[urlProp];
    return ajax.post<T>(url, params, config);
}

const ajax = {
    post<T>(
        url: string,
        params: Record<string, any>,
        config: AxiosRequestConfig = {}
    ): Promise<ResType<T>> {
        return new Promise((resolve, reject) => {
            axios.post(url, params, config).then(
                (res: AxiosResponse) => {
                    const { data = {} } = res;
                    resolve(data);
                },
                (err) => {
                    reject(err);
                }
            );
        });
    },
};

如何使用呢?

type HotelSpaceNode = {
    roomId: string;
    name: string;
    parentId: string;
}
api<HotelSpaceNode[]>('http://debug.aqara.com/getHotelSpaceNode', {}).then(({ code, result }) => {
      if (code === 0) {
          // 此時result的類型就是HotelSpaceNode[]
          this.spaceTreeData = result;
      }
  });

類型斷言(強制類型轉換)

這裏有個例子。
好比我定義了空對象,那麼我須要往對象中動態加入屬性,可是這個對象依賴於某個接口。

怎麼作呢?

type mapNodeType = {
    spaceName: string;
    spaceId: string;
    spaceType: number;
    parentId: string;
}

const mapNode = <mapNodeType>{};
const {
    level,
    childrenList,
    roomId,
    name,
    parentId,
    state,
    spaceState
} = currentNode;


mapNode.spaceId = roomId;
mapNode.spaceName = name;
mapNode.spaceType = level;
mapNode.parentId = parentId;

固然上面的例子用類型聲明更好一點,無論怎麼樣,都能提供類型推斷和更高的代碼可讀性。

還有一種是須要賦予默認值的時候。

genFloorNode(spaceNode: FloorSpaceNode = <FloorSpaceNode>{}) {
    const theSpaceNode = spaceNode;
    if (_.isEmpty(spaceNode)) {
        theSpaceNode.spaceType = SpaceType.FLOOR;
    }
    return new FloorSpaceNode(theSpaceNode);
}
相關文章
相關標籤/搜索