你可能不知道的typescript實(shí)用小技巧
前言
用了很久的 typescript,用了但感覺(jué)又沒(méi)完全用。因?yàn)楹芏?typescript 的特性沒(méi)有被使用,查看之前寫(xiě)的代碼滿屏的 any,這樣就容易導(dǎo)致很多 bug,也沒(méi)有發(fā)揮出 typescript 真正的“類(lèi)型”威力。本文總結(jié)了一些使用 typescript 的小技巧,以后使用 typescript 時(shí)可以運(yùn)用起來(lái)。
廢話不多說(shuō),直接上代碼。
函數(shù)重載
當(dāng)希望傳 user 參數(shù)時(shí),不傳 flag,傳 para 時(shí),傳 flag。就可以這樣寫(xiě):
interface User {
name: string;
age: number;
}
const user = {
name: 'Jack',
age: 123
};
class SomeClass {
public test(para: User): number;
public test(para: number, flag: boolean): number;
public test(para: User | number, flag?: boolean): number {
// 具體實(shí)現(xiàn)
return 1;
}
}
const someClass = new SomeClass();
// ok
someClass.test(user);
someClass.test(123, false);
// Error
// someClass.test(123);
//Argument of type 'number' is not assignable to parameter of type 'User'.
// someClass.test(user, false);
//Argument of type '{ name: string; age: number; }' is not assignable to parameter of type 'number'.
映射類(lèi)型
在了解映射類(lèi)型之前,需要了解 keyof, never, typeof, in。
keyof:keyof 取 interface 的鍵
interface Point {
x: number;
y: number;
}
// type keys = "x" | "y"
type keys = keyof Point;
never:永遠(yuǎn)不存在的值的類(lèi)型
官方描述:
the never type represents the type of values that never occur.
// 例子:進(jìn)行編譯時(shí)的全面的檢查
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 這里 foo 被收窄為 string 類(lèi)型
} else if (typeof foo === "number") {
// 這里 foo 被收窄為 number 類(lèi)型
} else {
// foo 在這里是 never
const check: never = foo;
}
}
使用 never 避免出現(xiàn)新增了聯(lián)合類(lèi)型沒(méi)有對(duì)應(yīng)的實(shí)現(xiàn),目的就是寫(xiě)出類(lèi)型絕對(duì)安全的代碼。
typeof:取某個(gè)值的 type
const a: number = 3 // 相當(dāng)于: const b: number = 4 const b: typeof a = 4
in:檢查一個(gè)對(duì)象上是否存在一個(gè)屬性
interface A {
x: number;
}
interface B {
y: string;
}
function doStuff(q: A | B) {
if ('x' in q) {
// q: A
} else {
// q: B
}
}
映射類(lèi)型就是將一個(gè)類(lèi)型映射成另外一個(gè)類(lèi)型,簡(jiǎn)單理解就是新類(lèi)型以相同的形式去轉(zhuǎn)換舊類(lèi)型的每個(gè)屬性。
Partial, Readonly, Nullable, Required
- Partial 將每個(gè)屬性轉(zhuǎn)換為可選屬性
- Readonly 將每個(gè)屬性轉(zhuǎn)換為只讀屬性
- Nullable 轉(zhuǎn)換為舊類(lèi)型和null的聯(lián)合類(lèi)型
- Required 將每個(gè)屬性轉(zhuǎn)換為必選屬性
type Partial<T> = {
[P in keyof T]?: T[P];
}
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
type Nullable<T> = {
[P in keyof T]: T[P] | null
}
type Required<T> = {
[P in keyof T]-?: T[P]
}
interface Person {
name: string;
age: number;
}
type PersonPartial = Partial<Person>;
type PersonReadonly = Readonly<Person>;
type PersonNullable = Nullable<Person>;
type PersonPartial = {
name?: string | undefined;
age?: number | undefined;
}
type PersonReadonly = {
readonly name: string;
readonly age: number;
}
type PersonNullable = {
name: string | null;
age: number | null;
}
interface Props {
a?: number;
b?: string;
}
const obj: Props = { a: 5 };
const obj2: Required<Props> = { a: 5 };
// Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
Pick, Record
- Pick 選取一組屬性指定新類(lèi)型
- Record 創(chuàng)建一組屬性指定新類(lèi)型,常用來(lái)聲明普通Object對(duì)象
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
type Record<K extends keyof any, T> = {
[P in K]: T;
}
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
todo; // = const todo: TodoPreview
interface PageInfo {
title: string;
}
type Page = "home" | "about" | "contact";
const nav: Record<Page, PageInfo> = {
about: { title: "title1" },
contact: { title: "title2" },
home: { title: "title3" },
};
nav.about; // = const nav: Record
Exclude, Omit
- Exclude 去除交集,返回剩余的部分
- Omit 適用于鍵值對(duì)對(duì)象的Exclude,去除類(lèi)型中包含的鍵值對(duì)
type Exclude<T, U> = T extends U ? never : T
type Omit = Pick<T, Exclude<keyof T, K>>
// 相當(dāng)于: type A = 'a'
type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "a",
completed: false,
};
ReturnType
獲取返回值類(lèi)型,一般為函數(shù)
type ReturnType<T extends (...args: any) => any>
= T extends (...args: any) => infer R ? R : any;
declare function f1(): { a: number; b: string };
type T1 = ReturnType<typeof f1>;
// type T1 = {
// a: number;
// b: string;
// }
還有很多映射類(lèi)型,可查看Utility Types參考。
類(lèi)型斷言
類(lèi)型斷言用來(lái)明確的告訴 typescript 值的詳細(xì)類(lèi)型,合理使用能減少我們的工作量。
比如一個(gè)變量并沒(méi)有初始值,但是我們知道它的類(lèi)型信息(它可能是從后端返回)有什么辦法既能正確推導(dǎo)類(lèi)型信息,又能正常運(yùn)行了?有一種網(wǎng)上的推薦方式是設(shè)置初始值,然后使用 typeof 拿到類(lèi)型(可能會(huì)給其他地方用)。也可以使用類(lèi)型斷言可以解決這類(lèi)問(wèn)題:
interface User {
name: string;
age: number;
}
export default class someClass {
private user = {} as User;
}
枚舉
枚舉類(lèi)型分為數(shù)字類(lèi)型與字符串類(lèi)型,其中數(shù)字類(lèi)型的枚舉可以當(dāng)標(biāo)志使用:
enum AnimalFlags {
None = 0,
HasClaws = 1 << 0,
CanFly = 1 << 1,
HasClawsOrCanFly = HasClaws | CanFly
}
interface Animal {
flags: AnimalFlags;
[key: string]: any;
}
function printAnimalAbilities(animal: Animal) {
var animalFlags = animal.flags;
if (animalFlags & AnimalFlags.HasClaws) {
console.log('animal has claws');
}
if (animalFlags & AnimalFlags.CanFly) {
console.log('animal can fly');
}
if (animalFlags == AnimalFlags.None) {
console.log('nothing');
}
}
var animal = { flags: AnimalFlags.None };
printAnimalAbilities(animal); // nothing
animal.flags |= AnimalFlags.HasClaws;
printAnimalAbilities(animal); // animal has claws
animal.flags &= ~AnimalFlags.HasClaws;
printAnimalAbilities(animal); // nothing
animal.flags |= AnimalFlags.HasClaws | AnimalFlags.CanFly;
printAnimalAbilities(animal); // animal has claws, animal can fly
- 使用 |= 來(lái)添加一個(gè)標(biāo)志;
- 組合使用 &= 和 ~ 來(lái)清理一個(gè)標(biāo)志;
- | 來(lái)合并標(biāo)志。
這個(gè)或許不常用,在 typescript 關(guān)于 types 源碼中我們也可以看到類(lèi)似的代碼:

字符串類(lèi)型的枚舉可以維護(hù)常量:
const enum TODO_STATUS {
TODO = 'TODO',
DONE = 'DONE',
DOING = 'DOING'
}
function todos (status: TODO_STATUS): Todo[];
todos(TODO_STATUS.TODO)
元組
表示一個(gè)已知元素?cái)?shù)量和類(lèi)型的數(shù)組,各元素的類(lèi)型不必相同。
let x: [string, number]; x = ['hello', 10];
在發(fā)出不固定多個(gè)請(qǐng)求時(shí),可以應(yīng)用:
const requestList: any[] = [http.get<A>('http://some.1')]; // 設(shè)置為 any[] 類(lèi)型
if (flag) {
requestList[1] = (http.get<B>('http://some.2'));
}
const [ { data: a }, response ] = await Promise.all(requestList) as [Response<A>, Response<B>?]
范型
在定義泛型后,有兩種方式使用,一種是傳入泛型類(lèi)型,另一種使用類(lèi)型推斷。
declare function fn<T>(arg: T): T; // 定義一個(gè)泛型函數(shù)
const fn1 = fn<string>('hello'); // 第一種方式,傳入泛型類(lèi)型
string const fn2 = fn(1); // 第二種方式,從參數(shù) arg 傳入的類(lèi)型 number,來(lái)推斷出泛型 T 的類(lèi)型是 number
一個(gè)扁平數(shù)組結(jié)構(gòu)建樹(shù)形結(jié)構(gòu)例子:
// 轉(zhuǎn)換前數(shù)據(jù)
const arr = [
{ id: 1, parentId: 0, name: 'test1'},
{ id: 2, parentId: 1, name: 'test2'},
{ id: 3, parentId: 0, name: 'test3'}
];
// 轉(zhuǎn)化后
[ { id: 1, parentId: 0, name: 'test1',
childrenList: [ { id: 2, parentId: 1, name: 'test2', childrenList: [] } ] },
{ id: 3, parentId: 0, name: 'test3', childrenList: [] }
]
interface Item {
id: number;
parentId: number;
name: string;
}
// 傳入的 options 參數(shù)中,得到 childrenKey 的類(lèi)型,然后再傳給 TreeItem
interface Options<T extends string> {
childrenKey: T;
}
type TreeItem<T extends string> = Item & { [key in T]: TreeItem<T>[] | [] };
declare function listToTree<T extends string = 'children'>(list: Item[], options: Options<T>): TreeItem<T>[];
listToTree(arr, { childrenKey: 'childrenList' }).forEach(i => i.childrenList)
infer
表示在 extends 條件語(yǔ)句中待推斷的類(lèi)型變量。
type ParamType<T> = T extends (param: infer P) => any ? P : T;
這句話的意思是:如果 T 能賦值給 (param: infer P) => any,則結(jié)果是 (param: infer P) => any 類(lèi)型中的參數(shù) P,否則返回為 T。
interface User {
name: string;
age: number;
}
type Func = (user: User) => void
type Param = ParamType<Func>; // Param = User
type AA = ParamType<string>; // string
例子:
// [string, number] -> string | number type ElementOf<T> = T extends Array<infer E> ? E : never; type TTuple = [string, number]; type ToUnion = ElementOf<TTuple>; // string | number // T1 | T2 -> T1 & T2 type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; type Result = UnionToIntersection<T1 | T2>; // T1 & T2
總結(jié)
typescript 關(guān)于類(lèi)型限制還是非常強(qiáng)大的,由于文章有限,還有其他類(lèi)型比如聯(lián)合類(lèi)型,交叉類(lèi)型等讀者可自行翻閱資料查看。剛開(kāi)始接觸范型以及其各種組合會(huì)感覺(jué)不熟練,接下來(lái)在項(xiàng)目中會(huì)慢慢應(yīng)用,爭(zhēng)取將 bug 降至最低限度。
到此這篇關(guān)于typescript實(shí)用小技巧的文章就介紹到這了,更多相關(guān)typescript實(shí)用小技巧內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一起來(lái)學(xué)習(xí)JavaScript的BOM操作
這篇文章主要為大家詳細(xì)介紹了JavaScript BOM操作,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-03-03
強(qiáng)悍無(wú)比的WEB開(kāi)發(fā)好助手FireBug(Firefox Plugin)
強(qiáng)悍無(wú)比的WEB開(kāi)發(fā)好助手FireBug(Firefox Plugin)...2007-01-01
原生JS實(shí)現(xiàn)的碰撞檢測(cè)功能示例
這篇文章主要介紹了原生JS實(shí)現(xiàn)的碰撞檢測(cè)功能,涉及javascript鼠標(biāo)事件響應(yīng)及頁(yè)面圖形坐標(biāo)位置運(yùn)算、檢測(cè)相關(guān)操作技巧,需要的朋友可以參考下2018-05-05
ie中js創(chuàng)建checkbox默認(rèn)選中問(wèn)題探討
js創(chuàng)建checkbox默認(rèn)選中在某些特殊情況下還是比較實(shí)用的,下面有個(gè)不錯(cuò)的示例,大家可以參考下2013-10-10
淺談JavaScript的自動(dòng)垃圾收集機(jī)制
本文主要對(duì)JavaScript的自動(dòng)垃圾收集機(jī)制進(jìn)行簡(jiǎn)要分析,并介紹了垃圾收集的方式:標(biāo)記清除(mark-and-sweep)和引用計(jì)數(shù)(reference counting),需要的朋友一起來(lái)看下吧2016-12-12
基于Bootstrap框架菜鳥(niǎo)入門(mén)教程(推薦)
下面小編就為大家?guī)?lái)一篇基于Bootstrap框架菜鳥(niǎo)入門(mén)教程(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09

