TS內置類型與拓展

TS內置類型與拓展

TypeScript具有類型系統,且是JavaScript的超集,其可以編譯成普通的JavaScript代碼,也就是說,其是帶有類型檢查的JavaScript

內置類型

TypeScript提供了幾種實用程序類型來促進常見的類型轉換,這些類型在全局範圍內可用。

Partial

Partial<Type>構造一個類型使Type的所有屬性都設置爲可選。

/**
 * Make all properties in T optional
 */

type Partial<T> = {
    [P in keyof T]?: T[P];
};
interface Example {
    a: string;
    b: number;
}

type PartialExample = Partial<Example>;

/**
 * PartialExample
 * interface {
 *     a?: string | undefined;
 *     b?: number | undefined;
 * }
 */

Required

Required<Type>構造一個類型使Type的所有屬性都設置爲required,與Partial<Type>功能相反。

/**
 * Make all properties in T required
 */

type Required<T> = {
    [P in keyof T]-?: T[P];
};
interface Example {
    a?: string;
    b?: number;
}

type RequiredExample = Required<Example>;

/**
 * RequiredExample
 * interface {
 *     a: string;
 *     b: number;
 * }
 */

Readonly

Required<Type>構造一個類型使Type的所有屬性都設置爲readonly,這意味着構造類型的屬性都是隻讀的,不能被修改,這對使用Object.freeze()方法的對象非常有用。

/**
 * Make all properties in T readonly
 */

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
interface Example {
    a: string;
    b: number;
}

type ReadonlyExample = Readonly<Example>;

/**
 * ReadonlyExample
 * interface {
 *     readonly a: string;
 *     readonly b: number;
 * }
 */

Record

Record<Keys, Type>構造一個對象類型,其屬性鍵爲Keys,其屬性值爲Type,通常可以使用Record來表示一個對象。

/**
 * Construct a type with a set of properties K of type T
 */

type Record<K extends keyof any, T> = {
    [P in K]: T;
};
type RecordType = Record<string, string|number>;

const recordExample: RecordType ={
  a: 1,
  b: "1"
}

Pick

Pick<Type, Keys>通過從Type中選擇一組屬性Keys來構造一個類型。

/**
 * 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 Example {
    a: string;
    b: number;
    c: symbol;
}

type PickExample = Pick<Example, "a"|"b">;

/**
 * PickExample
 * interface {
 *     a: string;
 *     b: number;
 * }
 */

Omit

Omit<Type, Keys>通過從Type中選擇所有屬性然後刪除Keys來構造一個類型,與Pick<Type, Keys>功能相反。

/**
 * Construct a type with the properties of T except for those in type K.
 */

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
interface Example {
    a: string;
    b: number;
    c: symbol;
}

type OmitExample = Omit<Example, "a"|"b">;

/**
 * OmitExample
 * interface {
 *     c: symbol;
 * }
 */

Exclude

Exclude<UnionType, ExcludedMembers>通過從UnionType中排除可分配給ExcludedMembers的所有聯合成員來構造類型。

/**
 * Exclude from T those types that are assignable to U
 */

type Exclude<T, U> = T extends U ? never : T;
type ExcludeExample = Exclude<"a"|"b"|"c"|"z", "a"|"b"|"d">;

/**
 * ExcludeExample
 * "c" | "z"
 */

Extract

Extract<Type, Union>通過從Type中提取所有可分配給Union的聯合成員來構造一個類型,與Exclude<UnionType, ExcludedMembers>功能相反。

/**
 * Extract from T those types that are assignable to U
 */

type Extract<T, U> = T extends U ? T : never;
type ExtractExample = Extract<"a"|"b"|"c"|"z", "a"|"b"|"d">;

/**
 * ExtractExample
 * "a" | "b"
 */

NonNullable

NonNullable<Type>通過從Type中排除nullundefined來構造一個類型。

/**
 * Exclude null and undefined from T
 */

type NonNullable<T> = T extends null | undefined ? never : T;
type NonNullableExample = NonNullable<number|string|null|undefined>;

/**
 * NonNullableExample
 * string | number
 */

Parameters

Parameters<Type>從函數類型Type的參數中使用的類型構造元組類型。

/**
 * Obtain the parameters of a function type in a tuple
 */

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
type FnType = (a1: number, a2: string) => void;

type ParametersExample = Parameters<FnType>;

/**
 * ParametersExample
 * [a1: number, a2: string]
 */

ConstructorParameters

ConstructorParameters<Type>從構造函數類型的類型構造元組或數組類型,其產生一個包含所有參數類型的元組類型。

/**
 * Obtain the parameters of a constructor function type in a tuple
 */

type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
interface Example{
  fn(a: string): string;
}

interface ExampleConstructor{
    new(a: string, b: number): Example;
}

declare const Example: ExampleConstructor;

type ConstructorParametersExample = ConstructorParameters<ExampleConstructor>;

/**
 * ConstructorParametersExample
 * [a: string, b: number]
 */

ReturnType

ReturnType<Type>構造一個由函數Type的返回類型組成的類型。

/**
 * Obtain the return type of a function type
 */

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type FnType = (a1: number, a2: string) => string | number;

type ReturnTypeExample = ReturnType<FnType>;

/**
 * ReturnTypeExample
 * string | number
 */

InstanceType

InstanceType<Type>構造一個由Type中構造函數的實例類型組成的類型。

/**
 * Obtain the return type of a constructor function type
 */

type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
interface Example{
  fn(a: string): string;
}

interface ExampleConstructor{
    new(a: string, b: number): Example;
}

declare const Example: ExampleConstructor;

type InstanceTypeExample = InstanceType<ExampleConstructor>;

// const a: InstanceTypeExample = new Example("a", 1); // new ExampleConstructor => Example

/**
 * InstanceTypeExample
 * Example
 */

ThisParameterType

ThisParameterType<Type>提取函數類型的this參數的類型,如果函數類型沒有this參數,則爲unknown

/**
 * Extracts the type of the 'this' parameter of a function type, or 'unknown' if the function type has no 'this' parameter.
 */

type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;
function toHex(this: Number) {
  return this.toString(16);
}

type ThisParameterTypeExample = ThisParameterType<typeof toHex>;

console.log(toHex.apply(27)); // 1b

/**
 * ThisParameterTypeExample
 * Number
 */

OmitThisParameter

OmitThisParameter<Type>Type中移除this參數,如果Type沒有顯式聲明此參數,則結果只是Type,否則,從Type創建一個不帶此參數的新函數類型。泛型被刪除,只有最後一個重載簽名被傳播到新的函數類型中。

/**
 * Removes the 'this' parameter from a function type.
 */

type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;
function toHex(this: Number) {
  return this.toString(16);
}

type OmitThisParameterExample = OmitThisParameter<typeof toHex>;

const toHex27: OmitThisParameterExample = toHex.bind(27);
console.log(toHex27()); // 1b

/**
 * OmitThisParameterExample
 * () => string
 */

ThisType

ThisType<Type>可以在對象字面量中鍵入this,並提供通過上下文類型控制this類型的便捷方式,其只有在--noImplicitThis的選項下才有效。

/**
 * Marker for contextual 'this' type
 */
interface ThisType<T> { }
// const foo1 = {
//     bar() {
//          console.log(this.a); // error
//     }
// }

const foo2: { bar: () => void } & ThisType<{ a: number }> = {
    bar() {
         console.log(this.a); // ok
    }
}

Uppercase

Uppercase<StringType>StringType轉爲大寫,TS以內置關鍵字intrinsic來通過編譯期來實現。

/**
 * Convert string literal type to uppercase
 */

type Uppercase<S extends string> = intrinsic;
type UppercaseExample = Uppercase<"abc">;

/**
 * UppercaseExample
 * ABC
 */

Lowercase

Lowercase<StringType>StringType轉爲小寫。

/**
 * Convert string literal type to lowercase
 */

type Lowercase<S extends string> = intrinsic;
type LowercaseExample = Lowercase<"ABC">;

/**
 * LowercaseExample
 * abc
 */

Capitalize

Capitalize<StringType>StringType首字母轉爲大寫。

/**
 * Convert first character of string literal type to uppercase
 */

type Capitalize<S extends string> = intrinsic;
type CapitalizeExample = Capitalize<"abc">;

/**
 * CapitalizeExample
 * Abc
 */

Uncapitalize

Uncapitalize<StringType>StringType首字母轉爲小寫。

/**
 * Convert first character of string literal type to lowercase
 */

type Uncapitalize<S extends string> = intrinsic;
type UncapitalizeExample = Uncapitalize<"ABC">;

/**
 * CapitalizeExample
 * aBC
 */

拓展

TypeScript中常用的一些語法以及概念。

泛型

泛型Generics是指在定義函數、接口或類的時候,不預先指定具體的類型,而在使用的時候再指定類型的一種特性。舉一個簡單的例子,如果需要實現一個生成數組的函數,這個數組會填充默認值,這個數組填充的類型不需要事先指定,而可以在使用的時候指定。當然在這裏使用new Array組合fill函數是一個效果。

function createArray<T>(value: T, length: number): T[] {
  const result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

console.log(createArray<number>(1, 3)); // 不顯式地指定`number`也可以自動推斷

我們也可以約束T的類型只能爲numberstring

const createArray = <T extends number|string>(value: T, length: number): T[] => {
  const result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

console.log(createArray<number>(1, 3));
// console.log(createArray(true, 3)); // Argument of type 'boolean' is not assignable to parameter of type 'string | number'.(2345)

多個類型也可以相互約束,例如上邊的Pick,在這裏的K必須是Tkey的子集。

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

在傳遞泛型的時候可以爲T指定默認值,使用範型編寫class即泛型類也是完全支持的。

class Example<T = number> {
    public value: T;
    public add: (x: T, y: T) => T;
    constructor(value: T, add: (x: T, y: T) => T){
      this.value = value;
      this.add = add;
    }
}

let example = new Example<number>(1, (x, y) => x + y);
console.log(example.value); // 1
console.log(example.add(1, 2)); // 3

斷言

類型斷言Type Assertion可以用來手動指定一個值的類型,由於<Type>value的語法容易與TSX衝突,所以通常都是使用value as Type的語法。通常當TypeScript不確定一個聯合類型的變量到底是哪個類型的時候,我們只能訪問此聯合類型的所有類型中共有的屬性或方法。

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function getName(animal: Cat | Fish) {
    return animal.name;
}

而有時候,我們確實需要在還不確定類型的時候就訪問其中一個類型特有的屬性或方法。

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function isFish(animal: Cat | Fish) {
    if (typeof animal.swim === "function") { // Property 'swim' does not exist on type 'Cat | Fish'. Property 'swim' does not exist on type 'Cat'.(2339)
        return true;
    }
    return false;
}

上面的例子中,獲取animal.swim的時候會報錯,此時可以使用類型斷言,將animal斷言成Fish。當然這裏只是舉一個例子說明斷言的使用,因爲濫用斷言是不提倡的,類型斷言只能夠欺騙TypeScript編譯器,而無法避免運行時的錯誤,濫用類型斷言可能會導致運行時錯誤。

interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}

function isFish(animal: Cat | Fish) {
    if (typeof (animal as Fish).swim === "function") {
        return true;
    }
    return false;
}

單個斷言即value as Type是有一定條件的,當S類型是T類型的子集,或者T類型是S類型的子集時,S能被成功斷言成T。這是爲了在進行類型斷言時提供額外的安全性,完全毫無根據的斷言是危險的,如果你想這麼做,你可以使用any
如果認爲某個值value必定是某種類型Type,而單個斷言無法滿足要求,可以使用雙重斷言,即value as unknown as Type,使用value as any as Type也是同樣的效果,但是若使用雙重斷言,則可以打破要使得A能夠被斷言爲B,只需要A兼容BB兼容A即可的限制,將任何一個類型斷言爲任何另一個類型。通常來說除非迫不得已,不要使用雙重斷言。
此外類型斷言之所以不被稱爲類型轉換,是因爲類型轉換通常意味着某種運行時的支持,而類型斷言只會影響TypeScript編譯時的類型,類型斷言語句在編譯結果中會被刪除,也就是說類型斷言純粹是一個編譯時語法,同時其也是一種爲編譯器提供關於如何分析代碼的方法。
與類型斷言相關的還有一個!的表達式,其在TypeScript 2.7被加入,其稱爲definite assignment assertion顯式賦值斷言,顯式賦值斷言允許你在實例屬性和變量聲明之後加一個感嘆號!,來告訴TypeScript這個變量確實已被賦值,即使TypeScript不能分析出這個結果。

let x: number;
let y!: number;
console.log(x + x); // Variable 'x' is used before being assigned.(2454)
console.log(y + y); // ok

既然說到了!,那麼也可以說一下?,在interface?undefined並不是等效的,在下面的例子中,在b未將?聲明的情況下,其在interface下是requiredTypeScript認爲其是必須指定的key即使其值只能爲undefined

interface Example{
  a?: number;
  b: undefined;
}

const example1: Example = {}; // Property 'b' is missing in type '{}' but required in type 'Example'.(2741)
const example2: Example = { b: undefined }; // ok

infer

infer示在extends條件語句中待推斷的類型變量,也可以認爲其是一個佔位符,用以在使用時推斷。例如上邊的ReturnType就是通過infer進行推斷的,首先是範型約束了一個函數類型,然後在後邊進行infer佔位後進行推斷。

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

有一些應用,tupleunion,如[string, number, symbol] -> string | number | symbol

type ElementOf<T> = T extends Array<infer E> ? E : never;

type TTuple = [string, number, symbol];

type ToUnion = ElementOf<TTuple>; // string | number | symbol

還有一個比較離譜的實現。

type TTuple = [string, number, symbol];
type Res = TTuple[number]; // string | number | symbol

// https://stackoverflow.com/questions/44480644/string-union-to-string-array/45486495#45486495

還比如獲取函數參數的第一個參數類型。

type fn = (a: number, b: string, ddd: boolean) => void;

type FirstParameter<T> = T extends (args1: infer R, ...rest: any[]) => any ? R : never;

type firstArg = FirstParameter<fn>;  // number

函數重載

TypeScript允許聲明函數重載,即允許一個函數接受不同數量或類型的參數時,作出不同的處理。當然,最終聲明即從函數內部看到的真正聲明與所有重載兼容是很重要的。這是因爲這是函數體需要考慮的函數調用的真實性質。

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
    if (typeof x === "number") {
        return Number(x.toString().split("").reverse().join(""));
    } else if (typeof x === "string") {
        return x.split("").reverse().join("");
    }
}

還有一個比較實用的簡單例子,在ios上的Date對象是不接受形如2022-04-05 20:00:00的字符串去解析的,當在safari的控制檯執行時,會出現一些異常行爲。這個字符串的解析在谷歌瀏覽器或者安卓上就沒有問題,所以需要做一下兼容處理。

// safari
const date = new Date("2022-04-05 20:00:00");
console.log(date.getDay()); // NaN

// chrome
const date = new Date("2022-04-05 20:00:00");
console.log(date.getDay()); // 2

所以需要對時間日期對象做一下簡單的兼容,但是做兼容時又需要保證TS的聲明,這時就可以使用函數重載等方式處理。

function safeDate(): Date;
function safeDate(date: Date): Date;
function safeDate(timestamp: number): Date;
function safeDate(dateTimeStr: string): Date;
function safeDate(
    year: number,
    month: number,
    date?: number,
    hours?: number,
    minutes?: number,
    seconds?: number,
    ms?: number
): Date;
function safeDate(
    p1?: Date | number | string,
    p2?: number,
    p3?: number,
    p4?: number,
    p5?: number,
    p6?: number,
    p7?: number
): Date | never {
    if (p1 === void 0) {
        // 無參構建
        return new Date();
    } else if (p1 instanceof Date || (typeof p1 === "number" && p2 === void 0)) {
        // 第一個參數爲`Date`或者`Number`且無第二個參數
        return new Date(p1);
    } else if (typeof p1 === "number" && typeof p2 === "number") {
        // 第一和第二個參數都爲`Number`
        return new Date(p1, p2, p3 || 1, p4 || 0, p5 || 0, p6 || 0, p7 || 0);
    } else if (typeof p1 === "string") {
        // 第一個參數爲`String`
        return new Date(p1.replace(/-/g, "/"));
    }
    throw new Error("No suitable parameters");
}

console.log(safeDate("2022-04-05 20:00:00").getDay()); // 2
type DateParams =
    | []
    | [string]
    | [number, number?, number?, number?, number?, number?, number?]
    | [Date];
const safeDate = <T extends DateParams>(...args: T): Date => {
    const copyParams = args.slice(0);
    if (typeof copyParams[0] === "string") copyParams[0] = copyParams[0].replace(/-/g, "/");
    return new Date(...(args as ConstructorParameters<typeof Date>));
};

console.log(safeDate("2022-04-05 20:00:00").getDay()); // 2

聲明文件

對於全局變量的聲明文件主要有以下幾種語法:

  • declare var聲明全局變量。
  • declare function聲明全局方法。
  • declare class聲明全局類。
  • declare enum聲明全局枚舉類型。
  • declare namespace聲明含有子屬性的全局對象。
  • interfacetype聲明全局類型。
  • declare module拓展聲明。

我們可以通過declare關鍵字來告訴TypeScript,某些變量或者對象已經聲明,我們可以選擇把這些聲明放入.ts或者.d.ts裏。declare namespace表示全局變量是一個對象,包含很多子屬性。

// global.d.ts
declare namespace App {
    interface Utils {
        onload: <T extends unknown[]>(fn: (...args: T) => void, ...args: T) => void;
    }
}

declare interface Window{
  utils: App.Utils
}

// main.ts
window.utils = {
  onload: () => void 0
}

對於模塊的聲明文件主要有以下幾種語法:

  • export導出變量。
  • export namespace導出含有子屬性的對象。
  • export default ES6默認導出。
  • export = 導出CommonJs模塊。

模塊的聲明文件與全局變量的聲明文件有很大區別,在模塊的聲明文件中,使用declare不再會聲明一個全局變量,而只會在當前文件中聲明一個局部變量,只有在聲明文件中使用export導出,然後在使用方import導入後,纔會應用到這些類型聲明,如果想使用模塊的聲明文件而並沒有實際的export時,通常會顯示標記一個空導出export {}。對於模塊的聲明文件我們更推薦使用 ES6標準的export defaultexport

// xxx.ts
export const name: string = "1";

// xxxxxx.ts
import { name } from "xxx.ts";
console.log(name); // 1 // typeof name === "string"

如果是需要擴展原有模塊的話,需要在類型聲明文件中先引用原有模塊,再使用declare module擴展原有模塊。

// xxx.d.ts
import * as moment from "moment";

declare module "moment" {
    export function foo(): moment.CalendarKey;
}

// xxx.ts
import * as moment from "moment";
moment.foo();
import Vue from "vue";

declare module "vue/types/vue" {
    interface Vue {
        copy: (str: string) => void;
    }
}

還有一些諸如.vue文件、.css.scss文件等,需要在全局中進行聲明其import時對象的類型。

declare module "*.vue" {
    import Vue from "vue/types/vue";
    export default Vue;
}
declare module "*.module.css" {
  const classes: { readonly [key: string]: string };
  export default classes;
}
declare module "*.module.scss" {
  const classes: { readonly [key: string]: string };
  export default classes;
}

在聲明文件中,還可以通過三斜線指令即///來導入另一個聲明文件,在全局變量的聲明文件中,是不允許出現importexport關鍵字的,一旦出現了,那麼他就會被視爲一個模塊或UMD庫,就不再是全局變量的聲明文件了,故當我們在書寫一個全局變量的聲明文件時,如果需要引用另一個庫的類型,那麼就必須用三斜線指令了。

// types/jquery-plugin/index.d.ts
/// <reference types="jquery" />
declare function foo(options: JQuery.AjaxSettings): string;

// src/index.ts
foo({});

協變與逆變

子類型在編程理論上是一個複雜的話題,而他的複雜之處來自於一對經常會被混淆的現象,我們稱之爲協變與逆變,在這裏引用兩篇文章以及實例。
首先是 這篇文章 對於協變與逆變的描述。協變即類型收斂,逆變即類型發散。

type Color = {}

type Red = {
  red: any;
}

let c: Color = {};
let r: Red = {red: 1};

// 變量類型是協變的
c = r; // Red類型收斂爲Color類型
r = c; // 報錯

// 函數參數類型是逆變的
let useC: (c: Color) => number;
let useR: (r: Red) => number;

useC = useR; // 協變,類型收斂。開啓strictFunctionTypes:true後將報錯,變爲逆變。
// useC執行傳入Color類型,執行的是useR,Color發散爲Red類型,發生錯誤。

useR = useC; // 逆變,類型發散。
// useR執行傳入Red類型,執行的是useC,Red類型收斂爲Color類型。

// 函數返回值類型是協變的
let useC2: () => Color = () => ({});
let useR2: () => Red = () => ({red: 1});

useC2 = useR2;
useR2 = useC2; // 報錯

除了函數參數類型是逆變,都是協變。 將一個函數賦給另一個函數變量時,要保證參數類型發散,即比目標類型範圍小。 目標函數執行時是執行的原函數,傳入的參數類型會收斂爲原函數參數類型。協變表示類型收斂,即類型範圍縮小或不變。逆變反之。本質是執行時類型收斂是安全的。

還有 這篇文章 對於協變與逆變的描述。
開始文章之前我們先約定如下的標記,A≼B意味着AB的子類型;A → B指的是以A爲參數類型,以B爲返回值類型的函數類型;x : A意味着x的類型爲A
假設我有如下三種類型:Greyhound ≼ Dog ≼ Animal
Greyhound灰狗是Dog狗的子類型,而Dog則是Animal動物的子類型,由於子類型通常是可傳遞的,因此我們也稱GreyhoundAnimal的子類型,問題: 以下哪種類型是Dog → Dog的子類型呢。

  1. Greyhound → Greyhound
  2. Greyhound → Animal
  3. Animal → Animal
  4. Animal → Greyhound

讓我們來思考一下如何解答這個問題,首先我們假設f是一個以Dog → Dog爲參數的函數,它的返回值並不重要,爲了具體描述問題,我們假設函數結構體是這樣的f :(Dog → Dog ) → String,現在我想給函數f傳入某個函數g來調用,我們來瞧瞧當g爲以上四種類型時,會發生什麼情況。

1.我們假設g : Greyhound → Greyhoundf(g)的類型是否安全?
不安全,因爲在f內調用它的參數(g)函數時,使用的參數可能是一個不同於灰狗但又是狗的子類型,例如GermanShepherd牧羊犬。
2.我們假設g : Greyhound → Animalf(g)的類型是否安全?
不安全。理由同1
3.我們假設g : Animal → Animalf(g)的類型是否安全?
不安全。因爲f有可能在調用完參數之後,讓返回值也就是Animal動物狗叫,並非所有動物都會狗叫。
4.我們假設g : Animal → Greyhoundf(g)的類型是否安全?
是的,它的類型是安全的,首先f可能會以任何狗的品種來作爲參數調用,而所有的狗都是動物,其次,它可能會假設結果是一條狗,而所有的灰狗都是狗。

如上所述,我們得出結論(Animal → Greyhound) ≼ (Dog → Dog)返回值類型很容易理解,灰狗是狗的子類型,但參數類型則是相反的,動物是狗的父類。用合適的術語來描述這個奇怪的表現,可以說我們允許一個函數類型中,返回值類型是協變的,而參數類型是逆變的。返回值類型是協變的,意思是A ≼ B就意味着(T → A ) ≼ ( T → B ),參數類型是逆變的,意思是A ≼ B就意味着(B → T ) ≼ ( A → T )AB的位置顛倒過來了。一個有趣的現象是在TypeScript中,參數類型是雙向協變的,也就是說既是協變又是逆變的,而這並不安全,但是現在你可以在TypeScript 2.6版本中通過--strictFunctionTypes--strict標記來修復這個問題。

tsconfig.json

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5" /* target用於指定編譯之後的版本目標: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
    "module": "commonjs" /* 用來指定要使用的模塊標準: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
    "lib": ["es6", "dom"] /* lib用於指定要包含在編譯中的庫文件 */,
    "allowJs": true,                       /* allowJs設置的值爲true或false,用來指定是否允許編譯js文件,默認是false,即不編譯js文件 */
    "checkJs": true,                       /* checkJs的值爲true或false,用來指定是否檢查和報告js文件中的錯誤,默認是false */
    "jsx": "preserve",                     /* 指定jsx代碼用於的開發環境: 'preserve', 'react-native', or 'react'. */
    "declaration": true,                   /* declaration的值爲true或false,用來指定是否在編譯的時候生成相應的".d.ts"聲明文件。如果設爲true,編譯每個ts文件之後會生成一個js文件和一個聲明文件。但是declaration和allowJs不能同時設爲true */
    "declarationMap": true,                /* 值爲true或false,指定是否爲聲明文件.d.ts生成map文件 */
    "sourceMap": true,                     /* sourceMap的值爲true或false,用來指定編譯時是否生成.map文件 */
    "outFile": "./",                       /* outFile用於指定將輸出文件合併爲一個文件,它的值爲一個文件路徑名。比如設置爲"./dist/main.js",則輸出的文件爲一個main.js文件。但是要注意,只有設置module的值爲amd和system模塊時才支持這個配置 */
    "outDir": "./",                        /* outDir用來指定輸出文件夾,值爲一個文件夾路徑字符串,輸出的文件都將放置在這個文件夾 */
    "rootDir": "./",                       /* 用來指定編譯文件的根目錄,編譯器會在根目錄查找入口文件,如果編譯器發現以rootDir的值作爲根目錄查找入口文件並不會把所有文件加載進去的話會報錯,但是不會停止編譯 */
    "composite": true,                     /* 是否編譯構建引用項目  */
    "incremental": true,                   /* 是否啓用增量編譯*/
    "tsBuildInfoFile": "./",               /* 指定文件用來存儲增量編譯信息 */
    "removeComments": true,                /* removeComments的值爲true或false,用於指定是否將編譯後的文件中的註釋刪掉,設爲true的話即刪掉註釋,默認爲false */
    "noEmit": true,                        /* 不生成編譯文件,這個一般比較少用 */
    "importHelpers": true,                 /* importHelpers的值爲true或false,指定是否引入tslib裏的輔助工具函數,默認爲false */
    "downlevelIteration": true,            /* 當target爲'ES5' or 'ES3'時,爲'for-of', spread, and destructuring'中的迭代器提供完全支持 */
    "isolatedModules": true,               /* isolatedModules的值爲true或false,指定是否將每個文件作爲單獨的模塊,默認爲true,它不可以和declaration同時設定 */
    "newLine": "lf",                       /* 指定換行符。可選`crlf`和`LF`兩種 */

    /* Strict Type-Checking Options */
    "strict": true /* strict的值爲true或false,用於指定是否啓動所有類型檢查,如果設爲true則會同時開啓下面這幾個嚴格類型檢查,默認爲false */,
    "noImplicitAny": true,                 /* noImplicitAny的值爲true或false,如果我們沒有爲一些值設置明確的類型,編譯器會默認認爲這個值爲any,如果noImplicitAny的值爲true的話。則沒有明確的類型會報錯。默認值爲false */
    "strictNullChecks": true,              /* strictNullChecks爲true時,null和undefined值不能賦給非這兩種類型的值,別的類型也不能賦給他們,除了any類型。還有個例外就是undefined可以賦值給void類型 */
    "strictFunctionTypes": true,           /* strictFunctionTypes的值爲true或false,用於指定是否使用函數參數雙向協變檢查 */
    "strictBindCallApply": true,           /* 設爲true後會對bind、call和apply綁定的方法的參數的檢測是嚴格檢測的 */
    "strictPropertyInitialization": true,  /* 設爲true後會檢查類的非undefined屬性是否已經在構造函數裏初始化,如果要開啓這項,需要同時開啓strictNullChecks,默認爲false */
   "noImplicitThis": true,                /* 當this表達式的值爲any類型的時候,生成一個錯誤 */
    "alwaysStrict": true,                  /* alwaysStrict的值爲true或false,指定始終以嚴格模式檢查每個模塊,並且在編譯之後的js文件中加入"use strict"字符串,用來告訴瀏覽器該js爲嚴格模式 */

    /* Additional Checks */
    "noUnusedLocals": true,                /* 用於檢查是否有定義了但是沒有使用的變量,對於這一點的檢測,使用eslint可以在你書寫代碼的時候做提示,你可以配合使用。它的默認值爲false */
    "noUnusedParameters": true,            /* 用於檢查是否有在函數體中沒有使用的參數,這個也可以配合eslint來做檢查,默認爲false */
    "noImplicitReturns": true,             /* 用於檢查函數是否有返回值,設爲true後,如果函數沒有返回值則會提示,默認爲false */
    "noFallthroughCasesInSwitch": true,    /* 用於檢查switch中是否有case沒有使用break跳出switch,默認爲false */

    /* Module Resolution Options */
    "moduleResolution": "node",            /* 用於選擇模塊解析策略,有'node'和'classic'兩種類型' */
    "baseUrl": "./",                       /* baseUrl用於設置解析非相對模塊名稱的基本目錄,相對模塊不會受baseUrl的影響 */
    "paths": {},                           /* 用於設置模塊名稱到基於baseUrl的路徑映射 */
    "rootDirs": [],                        /* rootDirs可以指定一個路徑列表,在構建時編譯器會將這個路徑列表中的路徑的內容都放到一個文件夾中 */
    "typeRoots": [],                       /* typeRoots用來指定聲明文件或文件夾的路徑列表,如果指定了此項,則只有在這裏列出的聲明文件纔會被加載 */
    "types": [],                           /* types用來指定需要包含的模塊,只有在這裏列出的模塊的聲明文件纔會被加載進來 */
    "allowSyntheticDefaultImports": true,  /* 用來指定允許從沒有默認導出的模塊中默認導入 */
    "esModuleInterop": true /* 通過爲導入內容創建命名空間,實現CommonJS和ES模塊之間的互操作性 */,
    "preserveSymlinks": true,              /* 不把符號鏈接解析爲其真實路徑,具體可以瞭解下webpack和nodejs的symlink相關知識 */

    /* Source Map Options */
    "sourceRoot": "",                      /* sourceRoot用於指定調試器應該找到TypeScript文件而不是源文件位置,這個值會被寫進.map文件裏 */
    "mapRoot": "",                         /* mapRoot用於指定調試器找到映射文件而非生成文件的位置,指定map文件的根路徑,該選項會影響.map文件中的sources屬性 */
    "inlineSourceMap": true,               /* 指定是否將map文件的內容和js文件編譯在同一個js文件中,如果設爲true,則map的內容會以//# sourceMappingURL=然後拼接base64字符串的形式插入在js文件底部 */
    "inlineSources": true,                 /* 用於指定是否進一步將.ts文件的內容也包含到輸入文件中 */

    /* Experimental Options */
    "experimentalDecorators": true /* 用於指定是否啓用實驗性的裝飾器特性 */
    "emitDecoratorMetadata": true,         /* 用於指定是否爲裝飾器提供元數據支持,關於元數據,也是ES6的新標準,可以通過Reflect提供的靜態方法獲取元數據,如果需要使用Reflect的一些方法,需要引入ES2015.Reflect這個庫 */
  }
  "files": [], // files可以配置一個數組列表,裏面包含指定文件的相對或絕對路徑,編譯器在編譯的時候只會編譯包含在files中列出的文件,如果不指定,則取決於有沒有設置include選項,如果沒有include選項,則默認會編譯根目錄以及所有子目錄中的文件。這裏列出的路徑必須是指定文件,而不是某個文件夾,而且不能使用* ? **/ 等通配符
  "include": [],  // include也可以指定要編譯的路徑列表,但是和files的區別在於,這裏的路徑可以是文件夾,也可以是文件,可以使用相對和絕對路徑,而且可以使用通配符,比如"./src"即表示要編譯src文件夾下的所有文件以及子文件夾的文件
  "exclude": [],  // exclude表示要排除的、不編譯的文件,它也可以指定一個列表,規則和include一樣,可以是文件或文件夾,可以是相對路徑或絕對路徑,可以使用通配符
  "extends": "",   // extends可以通過指定一個其他的tsconfig.json文件路徑,來繼承這個配置文件裏的配置,繼承來的文件的配置會覆蓋當前文件定義的配置。TS在3.2版本開始,支持繼承一個來自Node.js包的tsconfig.json配置文件
  "compileOnSave": true,  // compileOnSave的值是true或false,如果設爲true,在我們編輯了項目中的文件保存的時候,編輯器會根據tsconfig.json中的配置重新生成文件,不過這個要編輯器支持
  "references": [],  // 一個對象數組,指定要引用的項目
}

每日一題

https://github.com/WindrunnerMax/EveryDay

參考

https://www.typescriptlang.org/play/
https://zhuanlan.zhihu.com/p/443995763
https://zhuanlan.zhihu.com/p/353156044
https://segmentfault.com/q/1010000040197076
https://www.cnblogs.com/terrymin/p/13897214.html
https://www.cnblogs.com/wangleicode/p/10937707.html
https://blog.csdn.net/qq_43869822/article/details/121664818
https://tslang.baiqian.ltd/release-notes/typescript-2.7.html
https://www.typescriptlang.org/docs/handbook/utility-types.html
https://levelup.gitconnected.com/intrinsic-types-in-typescript-8b9f814410d
https://jkchao.github.io/typescript-book-chinese/tips/covarianceAndContravariance.html
https://github.com/xcatliu/typescript-tutorial/blob/master/basics/declaration-files.md
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章