注意你須要引入 strict: true (或者至少 noImplicitThis: true,這是 strict 模式的一部分) 以利用組件方法中 this 的類型檢查,不然它會始終被看做 any 類型。
須要注意如下的配置項,否則類型推斷不能正常運做vue
// 啓用全部嚴格類型檢查選項 "strict": true, // 禁止this 值爲 any "noImplicitThis": true,
vue.extend方法已經聲明瞭參數類型,因此vue.extend能夠爲你提供類型推斷。
舉個例子,看下面一段代碼:ios
type vueExtend = (option: { name: string, $store: object, $route: object }) => void; const vueInstance: vueExtend = ({ $ }) {};
平時咱們寫的 $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實例類型,這是有使用場景的。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中的類型得益於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' } } })
跟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中存在異常。
這是由於computed的異常阻斷了類型的進一步推斷。
以下狀況,此時已經不能推斷出prop裏面的屬性:
解決方法就是,對這種寫法的類型申明補全。
讓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方法作了類型推論。
/\* 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-toolbelt
一篇關於高級類型的博文:
【速查手冊】TypeScript 高級類型 cheat sheet
不一樣的請求接口,返回不一樣的數據類型,那麼咱們如何可以定義這種類型呢?咱們很容易想到用泛型。
可是每一個接口都會返回固定的格式:
{ 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); }