TypeScript 高級類型入門手冊:附大量代碼實例(收藏!)

TypeScript 是一種類型化的語言,允許你指定變量的類型,函數參數,返回的值和對象屬性。

你可以把本文看做一個帶有示例的 TypeScript 高級類型備忘單

讓我們開始吧!

Intersection Types(交叉類型)

交叉類型是一種將多種類型組合爲一種類型的方法。這意味着你可以將給定的類型 A 與類型 B 或更多類型合併,並獲得具有所有屬性的單個類型。

type LeftType = {
    id: number;
    left: string;
};

type RightType = {
    id: number;
    right: string;
};

type IntersectionType = LeftType & RightType;

function showType(args: IntersectionType) {
    console.log(args);
}

showType({ id: 1, left: 'test', right: 'test' });
// Output: {id: 1, left: "test", right: "test"}

如你所見,IntersectionType組合了兩種類型 -LeftTypeRightType,並使用符號形成了交叉類型。

Union Types(聯合類型)

聯合類型使你可以賦予同一個變量不同的類型

type UnionType = string | number;

function showType(arg: UnionType) {
    console.log(arg);
}

showType('test');
// Output: test

showType(7);
// Output: 7

函數showType是一個聯合類型函數,它接受字符串或者數字作爲參數。

Generic Types(泛型)

泛型類型是複用給定類型的一部分的一種方式。它有助於捕獲作爲參數傳遞的類型 T。

優點: 創建可重用的函數,一個函數可以支持多種類型的數據。這樣開發者就可以根據自己的數據類型來使用函數

泛型函數

function showType<T>(args: T) {
    console.log(args);
}

showType('test');
// Output: "test"

showType(1);
// Output: 1

如何創建泛型類型: 需要使用<>並將 T(名稱可自定義) 作爲參數傳遞。上面的 🌰 栗子中, 我們給 showType 添加了類型變量 TT幫助我們捕獲用戶傳入的參數的類型 (比如:number/string) 之後我們就可以使用這個類型

我們把 showType 函數叫做泛型函數,因爲它可以適用於多個類型

泛型接口

interface GenericType<T> {
    id: number;
    name: T;
}

function showType(args: GenericType<string>) {
    console.log(args);
}

showType({ id: 1, name: 'test' });
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType<number>) {
    console.log(args);
}

showTypeTwo({ id: 1, name: 4 });
// Output: {id: 1, name: 4}

在上面的栗子中,聲明瞭一個 GenericType 接口,該接口接收泛型類型 T, 並通過類型 T來約束接口內 name 的類型

注: 泛型變量約束了整個接口後,在實現的時候,必須指定一個類型

因此在使用時我們可以將name設置爲任意類型的值,示例中爲字符串或數字

多參數的泛型類型

interface GenericType<T, U> {
    id: T;
    name: U;
}

function showType(args: GenericType<number, string>) {
    console.log(args);
}

showType({ id: 1, name: 'test' });
// Output: {id: 1, name: "test"}

function showTypeTwo(args: GenericType<string, string[]>) {
    console.log(args);
}

showTypeTwo({ id: '001', name: ['This''is''a''Test'] });
// Output: {id: "001", name: Array["This""is""a""Test"]}

泛型類型可以接收多個參數。在上面的代碼中,我們傳入兩個參數:TU,然後將它們用作id,name的類型。也就是說,我們現在可以使用該接口並提供不同的類型作爲參數。

Utility Types

TypeScript 內部也提供了很多方便實用的工具,可幫助我們更輕鬆地操作類型。如果要使用它們,你需要將類型傳遞給<>

Partial

Partial 允許你將T類型的所有屬性設爲可選。它將在每一個字段後面添加一個?

interface PartialType {
    id: number;
    firstName: string;
    lastName: string;
}

/*
等效於
interface PartialType {
  id?: number
  firstName?: string
  lastName?: string
}
*/

function showType(args: Partial<PartialType>) {
    console.log(args);
}

showType({ id: 1 });
// Output: {id: 1}

showType({ firstName: 'John', lastName: 'Doe' });
// Output: {firstName: "John", lastName: "Doe"}

上面代碼中聲明瞭一個PartialType接口,它用作函數showType()的參數的類型。爲了使所有字段都變爲可選,我們使用Partial關鍵字並將PartialType類型作爲參數傳遞。

Required

將某個類型裏的屬性全部變爲必選項

interface RequiredType {
    id: number;
    firstName?: string;
    lastName?: string;
}

function showType(args: Required<RequiredType>) {
    console.log(args);
}

showType({ id: 1, firstName: 'John', lastName: 'Doe' });
// Output: { id: 1, firstName: "John", lastName: "Doe" }

showType({ id: 1 });
// Error: Type '{ id: number: }' is missing the following properties from type 'Required<RequiredType>': firstName, lastName

上面的代碼中,即使我們在使用接口之前先將某些屬性設爲可選,但Required被加入後也會使所有屬性成爲必選。如果省略某些必選參數,TypeScript 將報錯。

Readonly

會轉換類型的所有屬性,以使它們無法被修改

interface ReadonlyType {
    id: number;
    name: string;
}

function showType(args: Readonly<ReadonlyType>) {
    args.id = 4;
    console.log(args);
}

showType({ id: 1, name: 'Doe' });
// Error: Cannot assign to 'id' because it is a read-only property.

我們使用Readonly來使ReadonlyType的屬性不可被修改。也就是說,如果你嘗試爲這些字段之一賦予新值,則會引發錯誤。

除此之外,你還可以在指定的屬性前面使用關鍵字readonly使其無法被重新賦值

interface ReadonlyType {
    readonly id: number;
    name: string;
}

Pick

此方法允許你從一個已存在的類型 T中選擇一些屬性作爲K, 從而創建一個新類型

即 抽取一個類型 / 接口中的一些子集作爲一個新的類型

T代表要抽取的對象 K有一個約束: 一定是來自T所有屬性字面量的聯合類型 新的類型 / 屬性一定要從K中選取,

/**
    源碼實現
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};
interface PickType {
    id: number;
    firstName: string;
    lastName: string;
}

function showType(args: Pick<PickType, 'firstName' | 'lastName'>) {
    console.log(args);
}

showType({ firstName: 'John', lastName: 'Doe' });
// Output: {firstName: "John"}

showType({ id: 3 });
// Error: Object literal may only specify known properties, and 'id' does not exist in type 'Pick<PickType, "firstName" | "lastName">'

Pick 與我們前面討論的工具有一些不同,它需要兩個參數

Omit

Omit的作用與Pick類型正好相反。不是選擇元素,而是從類型T中刪除K個屬性。

interface PickType {
    id: number;
    firstName: string;
    lastName: string;
}

function showType(args: Omit<PickType, 'firstName' | 'lastName'>) {
    console.log(args);
}

showType({ id: 7 });
// Output: {id: 7}

showType({ firstName: 'John' });
// Error: Object literal may only specify known properties, and 'firstName' does not exist in type 'Pick<PickType, "id">'

Extract

提取T中可以賦值給U的類型 -- 取交集

Extract允許你通過選擇兩種不同類型中的共有屬性來構造新的類型。也就是從T中提取所有可分配給U的屬性。

interface FirstType {
    id: number;
    firstName: string;
    lastName: string;
}

interface SecondType {
    id: number;
    address: string;
    city: string;
}

type ExtractType = Extract<keyof FirstType, keyof SecondType>;
// Output: "id"

在上面的代碼中,FirstType接口和SecondType接口,都存在 id:number屬性。因此,通過使用Extract,即提取出了新的類型 {id:number}

Exclude

Exclude<T, U> -- 從 T 中剔除可以賦值給 U 的類型。

Extract不同,Exclude通過排除兩個不同類型中已經存在的共有屬性來構造新的類型。它會從T中排除所有可分配給U的字段。

interface FirstType {
    id: number;
    firstName: string;
    lastName: string;
}

interface SecondType {
    id: number;
    address: string;
    city: string;
}

type ExcludeType = Exclude<keyof FirstType, keyof SecondType>;

// Output; "firstName" | "lastName"

上面的代碼可以看到,屬性firstNamelastNameSecondType類型中不存在。通過使用Extract關鍵字,我們可以獲得T中存在而U中不存在的字段。

Record

此工具可幫助你構造具有給定類型T的一組屬性K的類型。將一個類型的屬性映射到另一個類型的屬性時,Record非常方便。

interface EmployeeType {
    id: number;
    fullname: string;
    role: string;
}

let employees: Record<number, EmployeeType> = {
    0: { id: 1, fullname: 'John Doe', role: 'Designer' },
    1: { id: 2, fullname: 'Ibrahima Fall', role: 'Developer' },
    2: { id: 3, fullname: 'Sara Duckson', role: 'Developer' },
};

// 0: { id: 1, fullname: "John Doe", role: "Designer" },
// 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
// 2: { id: 3, fullname: "Sara Duckson", role: "Developer" }

Record的工作方式相對簡單。在代碼中,它期望一個number作爲類型,這就是爲什麼我們將 0、1 和 2 作爲employees變量的鍵的原因。如果你嘗試使用字符串作爲屬性,則會引發錯誤, 因爲屬性是由EmployeeType給出的具有 ID,fullName 和 role 字段的對象。

NonNullable

-- 從 T 中剔除 nullundefined

type NonNullableType = string | number | null | undefined;

function showType(args: NonNullable<NonNullableType>) {
    console.log(args);
}

showType('test');
// Output: "test"

showType(1);
// Output: 1

showType(null);
// Error: Argument of type 'null' is not assignable to parameter of type 'string | number'.

showType(undefined);
// Error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'.

我們將類型NonNullableType作爲參數傳遞給NonNullableNonNullable通過排除nullundefined來構造新類型。也就是說,如果你傳遞可爲空的值,TypeScript 將引發錯誤。

順便說一句,如果將--strictNullChecks標誌添加到tsconfig文件,TypeScript 將應用非空性規則。

Mapped Types(映射類型)

映射類型允許你從一箇舊的類型,生成一個新的類型。

請注意,前面介紹的某些高級類型也是映射類型。如:

/*
Readonly, Partial和 Pick是同態的,但 Record不是。 因爲 Record並不需要輸入類型來拷貝屬性,所以它不屬於同態:
*/
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
type Partial<T> = {
    [P in keyof T]?: T[P];
};
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

Record;
type StringMap<T> = {
    [P in keyof T]: string;
};

function showType(arg: StringMap<{ id: number; name: string }>) {
    console.log(arg);
}

showType({ id: 1, name: 'Test' });
// Error: Type 'number' is not assignable to type 'string'.

showType({ id: 'testId', name: 'This is a Test' });
// Output: {id: "testId", name: "This is a Test"}

StringMap<>會將傳入的任何類型轉換爲字符串。就是說,如果我們在函數showType()中使用它,則接收到的參數必須是字符串 - 否則,TypeScript 將引發錯誤。

Type Guards(類型保護)

類型保護使你可以使用運算符檢查變量或對象的類型。這是一個條件塊,它使用typeofinstanceofin返回類型。

typescript 能夠在特定區塊中保證變量屬於某種確定類型。可以在此區塊中放心地引用此類型的屬性,或者調用此類型的方法

typeof

function showType(x: number | string) {
    if (typeof x === 'number') {
        return `The result is ${x + x}`;
    }
    throw new Error(`This operation can't be done on a ${typeof x}`);
}

showType("I'm not a number");
// Error: This operation can't be done on a string

showType(7);
// Output: The result is 14

什麼代碼中,有一個普通的 JavaScript 條件塊,通過typeof檢查接收到的參數的類型。

instanceof

class Foo {
    bar() {
        return 'Hello World';
    }
}

class Bar {
    baz = '123';
}

function showType(arg: Foo | Bar) {
    if (arg instanceof Foo) {
        console.log(arg.bar());
        return arg.bar();
    }

    throw new Error('The type is not supported');
}

showType(new Foo());
// Output: Hello World

showType(new Bar());
// Error: The type is not supported

像前面的示例一樣,這也是一個類型保護,它檢查接收到的參數是否是Foo類的一部分,並對其進行處理。

in

interface FirstType {
    x: number;
}
interface SecondType {
    y: string;
}

function showType(arg: FirstType | SecondType) {
    if ('x' in arg) {
        console.log(`The property ${arg.x} exists`);
        return `The property ${arg.x} exists`;
    }
    throw new Error('This type is not expected');
}

showType({ x: 7 });
// Output: The property 7 exists

showType({ y: 'ccc' });
// Error: This type is not expected

什麼的栗子中,使用in檢查參數對象上是否存在屬性x

Conditional Types(條件類型)

條件類型測試兩種類型,然後根據該測試的結果選擇其中一種。

一種由條件表達式所決定的類型, 表現形式爲 T extends U ? X : Y , 即如果類型T可以被賦值給類型U,那麼結果類型就是X類型,否則爲Y類型。

條件類型使類型具有了不唯一性,增加了語言的靈活性,

// 源碼實現
type NonNullable<T> = T extends null | undefined ? never : T;

// NotNull<T> 等價於 NoneNullable<T,U>

// 用法示例
type resType = NonNullable<string | number | null | undefined>; // string|number

上面的代碼中, NonNullable檢查類型是否爲 null,並根據該類型進行處理。正如你所看到的,它使用了 JavaScript 三元運算符。

感謝閱讀。

轉自: 望道同學

https://juejin.cn/post/6904150785966211086

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/QKYaYL_xJAj1SqeKBRuqIg