TypeScript進階(二)

接着上次TypeScript進階(一),此次把剩下的知識補充上。數組

TypeScript進階(二)

類型推論

類型推論又叫類型推斷。promise

以前對類型推論的基礎作了筆記,此次寫的是多類型聯合和上下文類型。dom

多類型聯合

多類型聯合的推論是指根據等號右邊推論出左邊。分佈式

// 多類型聯合
let arr7 = [1, 'abcd'];     // 根據右邊的[1, 'abcd'],推斷出arr7是(string | number)[]類型
arr7 = ['a', 4, 'd'];   // 這裏再次賦值(元素是number或string)是能夠的

// arr7 = [0, true]; // error

上下文類型

上下文類型的推論是指根據等號左邊推論出右邊。函數

舉例。this

// 上下文類型
window.onmousedown = (mouseEvent) => {  // 根據window.onmousedown這個對象,這裏推斷出mouseEvent是MouseEvent類型
    console.log(mouseEvent);
}

image.png

類型兼容性

深層次遞歸檢測

// 類型兼容性,深層次遞歸檢測
interface Geometry {
    count: number,
    info: {
        background: string,
        faceColor: string
    }
}

let geom1: Geometry;
geom1 = {
    count: 10,
    info: {                     // 這裏會往下檢測info對象
        background: 'black',
        faceColor: 'lightblue'
    }
}

函數兼容性

函數參數個數

賦給函數的參數個數要少於函數定義的參數個數。spa

舉個例子。3d

forEach((item, index, arr) => {
    // ...
});

// 這個函數定義了三個參數,但實際上,咱們使用的時候,能夠只傳一個參數

forEach((item) => {
    // ...
});
函數的參數類型
// 兩個函數的參數類型相同
let xx = (a: string) => 0;

let yy = (a: string) => 0;

xx = yy; // 沒問題
// 兩個函數的參數類型不一樣
let xx = (a: string) => 0;

let yy = (a: number) => 0;

xx = yy; // 報錯,由於參數類型不同
函數的可選參數和剩餘參數
const getSum = (arr: number[], callback: (...args: number[]) => number): number => {

    // 把第一個參數數組傳給回調函數
    // 回調函數返回接收了第一個參數的回調函數
    return  callback(...arr);
}

const sum1 = getSum([1, 2], (...args: number[]): number => {

    // 回調函數返回參數數組每一個元素的累加和
    return  args.reduce((a, b) => {
        return a + b;
    }, 0);
});

const sum2 = getSum([1, 8, 0], (arg1: number, arg2: number, arg3: number): number => {
    return arg1 + arg2 + arg3;
});

console.log(sum1);  // 3
console.log(sum2);  // 9
函數參數雙向協變
let func1 = (n1: number | string): void => {}
let func2 = (n1: number): void => {}

// func1 = func2; // error
func2 = func1; // right
返回值類型
let x = (): string | number =>  0
let y = (): string =>  'a'

x = y  // right
// y = x // error
函數重載
function merge(arg1: number, arg2: number): number
function merge(arg1: string, arg2: string): string
function merge(arg1: any, arg2: any) {
    return arg1 + arg2;
}

console.log(merge('1', '2').length);
// 2

function sum(arg1: number, arg2: number): number
function sum(arg1: any, arg2: any): any {
    return arg1 + arg2;
}

let func = merge;  // 這裏func的類型是一個對象接口,會有兩個重載的函數
// func = sum;   // error 若是再把sum賦值給func,由於sum少了一個重載函數,因此會報錯

枚舉兼容性

enum Status1 {
    On,
    Off
};

enum Status2 {
    Dog,
    Cat
};

let s1 = Status1.On;
s1 = 1; // right
s1 = 2; // right
// s1 = Status2.Dog; // error 不兼容

類的兼容性

類實例成員
class A1 {
    static age: number;
    constructor(public name: string) {}
}

class B {
    static age: string;
    constructor(public name: string) {}
}

class C {
    constructor(public name: number) {}
}

let a11: A1;
let b: B;
let c: C;

b = new B('Jack');

a11 = b; // 這裏沒問題,類A一、B的實例都是傳一個string類型參數,因此把b賦給a11實際上沒問題

c = new C(10);
// a11 = c; // 這裏有問題,類A1的實例傳string類型參數,類C的實例傳number類型參數,因此把c賦給a11有類型兼容問題
類的受保護成員
private
class A2 {
    age: number;
    constructor(age: number) {
        this.age = age;
    }
}

class B2 extends A2 {
    constructor(age: number) {
        super(age)
    }
}

class C2 {
    private age: number
    constructor(age: number) {
        this.age = age
    }
}

const a22: A2 = new B2(10); // 子類能夠賦值給父類類型的變量
// const a23: A2 = new C2(10); // 類C2含有私有屬性,不能賦值
protected
class A3 {
    protected age: number;
    constructor(age: number) {
        this.age = age;
    }
}

class B3 extends A3 {
    constructor(age: number) {
        super(age)
    }
}

class C3 {
    protected age: number
    constructor(age: number) {
        this.age = age
    }
}

const a3: A3 = new B3(10); // 子類能夠賦值給父類類型的變量

// const a32: A3 = new C3(10); // C3類並非A3類的子類,同時age屬性受保護,這裏有問題

泛型兼容性

interface Data<T> {}

let data1: Data<number>
let data2: Data<string>

data1 = {}
data2 = data1; // 這種狀況是能夠的

高級類型(一)

交叉類型

交叉類型是指多個類型的並集,使用&符號表示。code

const mergeFunc = <T, U>(arg1: T, arg2: U): T & U => {
    let res = {} as T & U;
    res = Object.assign(arg1, arg2);
    return  res;
}

console.log(mergeFunc({ a: 1 }, { b: 'b' }));
// {a: 1, b: "b"}

聯合類型

const getLengthFunc = (content: string | number): number => {
    return typeof content === 'string' ? content.length : content.toString().length;
}

console.log(getLengthFunc('abcd'));
// 4

console.log(getLengthFunc(1111009));
// 7

類型保護

定義方法進行類型保護
function isString (value: string | number): value is string {
    return typeof value === 'string';
}

const getLen = (arg: string | number): number => {
    if(isString(arg)) {
        return arg.length;
    } else {
        return arg.toString().length;
    }
}

console.log(getLen('aaaa'));
// 4

console.log(getLen(123456789));
// 9
typeof類型保護

若是是簡單的判斷,能夠直接使用typeof來作類型保護,而若是是比較複雜的判斷,則使用定義函數的方式來進行類型保護。以上例子改寫成使用typeof的類型保護方式以下。對象

const getLen = (arg: string | number): number => {
    if (typeof arg === 'string') {
        return arg.length;
    } else {
        return arg.toString().length;
    }
}

console.log(getLen('aaaa'));
// 4

console.log(getLen(123456789));
// 9

typeof只對string、number、boolean、symbol這幾種類型作保護。

typeof只作 === 或 !== 判斷的類型保護,像includes這樣的判斷不能實現類型保護。

instanceof作類型保護
class cClass1 {
    age = 18;
    constructor() {}
}

class cClass2 {
    name = 'Jack';
    constructor() {}
}

function getRandomItem () {
    return Math.random() > 0.5 ? new cClass1() : new cClass2();
}

const r1 = getRandomItem();
if (r1 instanceof cClass1) {
    console.log('This is a instance of cClass1');
} else {
    console.log('This is a instance of cClass2');
}
// 會根據隨機數的大小,返回相應實例的構造函數log

null/undefined

null和undefined是任何類型的子類型。

區別對待null和undefined
// number | undefined 、 number | null 、 number | null | undefined 是不同的
const getSum3 = (a: number, b?: number) => {
    return a + (b || 0);
}

console.log(getSum3(1));

image.png

類型斷言

// 類型斷言,使用"!"手動指明類型不是null或undefined
function getSplitStr (arg: number | null | undefined): string {
   
   arg = arg || 0.1;
   
    function getRes(prefix: string) {
        return `${ prefix }_${ arg!.toFixed().toString() }`
    }
    
    return  getRes('Y');
}

console.log(getSplitStr(7.3));
// Y_7

console.log(getSplitStr(null));
// Y_0

console.log(getSplitStr(undefined));
// Y_0

類型別名

定義樹狀結構類型
type Childs<T> = {
    current: T,
    child?: Childs<T>
}

const c9: Childs<object> = {
    current: {},
    child: {
        current: {}
    }
}
類型別名與接口兼容
// 類型別名與接口兼容
type Alias = {
    num: number
}

interface Inter {
    num: number
}

let alias: Alias = {
    num: 123
}

let inter: Inter = {
    num:  1
}

alias = inter;  // 沒問題

字面量類型

字符串字面量類型
// 字符串字面量類型
type StrType = 'str_type';

// let str: StrType = 's'; // error
let str: StrType = 'str_type'  // right
// 字符串字面量的聯合類型

type Direction = 'North' | 'East' | 'West' | 'South';

function getDirectionFirstLetter(direction: Direction) {
    return  direction.substr(0, 1);
}

console.log(getDirectionFirstLetter('East'));
// E
數字字面量類型
// 數字字面量類型
type Age = 18;

interface Info {
    name: string,
    age: Age
}

let p1: Info = {
    name: 'Tom',
    age: 18     //這個值必須跟Age類型別名的值一致
}

// let p1: Info = {
//    name: 'Tom',
//    age: 19   // error
}

可辨識聯合

可辨識聯合的兩要素:一、具備普通的單例類型屬性;二、一個類型別名包含了多個類型的聯合。

interface Square {
    kind: 'square',
    size: number
}

interface Rectangle {
    kind: 'rectangle',
    height: number,
    width: number
}

interface Circle {
    kind: 'circle',
    radius: number
}

type Shape = Square | Rectangle | Circle;

function getArea (s: Shape) {
    switch (s.kind) {
        case 'square': return s.size * s.size; break;
        case 'rectangle': return s.width * s.width; break;
        case 'circle': return Math.floor(Math.PI) * s.radius ** 2; break;
    }
}

console.log(getArea({
    kind: 'rectangle',
    width: 100,
    height: 100
}));
// 10000

console.log(getArea({
    kind: 'square',
    size: 4
}));
// 16

console.log(getArea({
    kind: 'circle',
    radius: 3
}));
// 27
完整性檢查
function assetNever (value: never): never {
    throw new Error(`Unexpected object ${ value }`);
}

function getArea (s: Shape): number {
    switch (s.kind) {
        case 'square': return s.size * s.size; break;
        case 'rectangle': return s.width * s.width; break;
        
        // case 'circle': return Math.floor(Math.PI) s.radius ** 2; break;
        
        default: return assetNever(s)
    }
}

這裏寫一個函數assetNever()用來檢查switch代碼的完整性,若是漏寫了case,default中會檢測到代碼的問題。

高級類型(二)

this類型

使用this返回實例,實現鏈式調用。

class Counter {
    constructor(public count: number) {}
    public add (v: number) {
        this.count += v;
        return this;
}

    public substract (v: number) {
        this.count -= v;
        return this;
    }
}

var count1 = new Counter(3);

console.log(count1.add(10));
// Counter {count: 13}

console.log(count1.add(10).substract(1));
// Counter {count: 22}

console.log(count1.add(9).substract(9).add(1));
// Counter {count: 23}
class powCounter extends Counter {
    constructor(public count: number = 0) {
        super(count);
    }

    pow (v: number) {
        this.count = this.count ** v;
        return this;
    }

}

var p2 = new powCounter(2);

console.log(p2.pow(3));
// powCounter {count: 8}

console.log(p2.substract(2));
// powCounter {count: 6}

索引類型

索引類型查詢操做符
// keyof
interface InfoInterfaceAdvanced {
    name: string,
    age: number
}

let infoProp: keyof InfoInterfaceAdvanced

infoProp = 'name'
infoProp = 'age'
// 泛型使用索引類型
function getValue<T, K extends keyof T> (obj: T, names: K[]):  T[K][] {
    return names.map((n) => obj[n]);
}

console.log(getValue({ width: 100, id: 'A001' }, ['width', 'id']));
// [100, "A001"]
function getProperty<O, K extends keyof O>(o: O, key: K):  O[K] {
    return o[key];
}
interface Objs<T> {
    [key: number]: T
}

let key1: keyof Objs<number>
interface Type {
    a: never;
    b: never;
    c: string;
    d: number;
    e: boolean;
    f: null;
    g: undefined;
    h: object
}

type Test = Type[keyof Type]

image.png

keyof不會查詢出never類型的屬性。

索引訪問操做符[]
interface InfoInterfaceAdvanced {
    name: string,
    age: number
}

type NameTypes = InfoInterfaceAdvanced['name'];  // type NameTypes = string

image.png

interface Objs22<T> {
    [key: string]: T
}

let key22: Objs22<number>['age']

image.png

映射類型

基礎
只讀屬性
interface InterInfo {
    width: number,
    color: string,
    height: number
}

// 映射
type ReadonlyInfoMap<T> = {
    readonly [P in keyof T]: T[P]
}

type ReadonlyInfo = ReadonlyInfoMap<InterInfo>;

這樣,類型別名ReadonlyInfo就是由InterInfo映射成的只讀屬性類型列表。

image.png

可選屬性

若是屬性是可選,則

type ReadonlyInfoMap<T> = {
    readonly [P in keyof T]?: T[P]
}

image.png

部分屬性
type Pick1<T, K extends keyof T> = {
    [P in K]: T[P]
}

type keyList = 'width' | 'color';
type ReadonlyInfo4 = Pick1<InterInfo, keyList>;

image.png

function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick1<T, K> {
    const res: any = {}
    keys.map((key) => res[key] = obj[key]);
    return res;
}

console.log(pick({ width: 100, radius: '50%' }, ['radius']));
// {radius: "50%"}
內置映射類型

也能夠使用內置類型,Readonly、Partial、Pick、Record。

// Readonly
type ReadonlyInfo2 = Readonly<InterInfo>;

image.png

// Partial
type ReadonlyInfo3 = Partial<InterInfo>;

image.png

// Pick
type ReadonlyInfo4 = Pick<InterInfo, keyList>;
// Record
// 把對象屬性值映射成其餘屬性值
function mapObject<K extends string | number, V, U> (obj: Record<K, V>, f: (x: V) => U): Record<K, U> {
    let res: any = {}
    for(const key in obj) {
        res[key] = f(obj[key]);
    }
    return res;
}

const names = { a: 'aa', b: 'bbb', c: 'cccc' };
const len = mapObject(names, (s) => s.length );

console.log(len);
// {a: 2, b: 3, c: 4}

由映射類型進行推斷

舉例。

type Proxy<V> = {
    get(): V
    set(newval: V): void
}

type Proxify<O> = {

    /**
     * 把屬性值傳給Proxy做爲Proxy的泛型
     * 每一個屬性都帶有get、set
     */
    [K in keyof O]: Proxy<O[K]>
}

function proxify<O>(obj: O): Proxify<O> {
    const result = {} as Proxify<O>;
    for (const key in obj) {

        // 每一個屬性都帶有set、get
        result[key] = {
            get: () => obj[key],
            set: (newval) => obj[key] = newval
        }
    }

    return  result;

}

let modal = {
    name: 'cancel',
    width:  500,
    msg: 'success',
    radius: '5%'
}

let proxyProps = proxify(modal);

console.log(proxyProps);
// {name: {…}, width: {…}, msg: {…}, radius: {…}}

console.log(proxyProps.name.get());
// cancel
// 拆包
function unproxify<O> (obj: Proxify<O>): O{
    const result = {} as  O;
    for (const  key  in  obj) {

        // 每一個屬性還原回原來的屬性值
        result[key] = obj[key].get()
    }

    return  result;

}

console.log(unproxify(proxyProps));
// {name: "cancel", width: 500, msg: "success", radius: "5%"}

增長和移除修飾符

添加修飾符使用+,移除修飾符使用-。

// 移除修飾符 -
type RemoveReadonlyInfo<T> = {
    -readonly [P in keyof T]-?: T[P]
}

type NotReadonly = RemoveReadonlyInfo<InterInfo>

元組和數組上的映射類型

type MapToPromise<T> = {
    [K in keyof T]: Promise<T[K]>
}

type Tuple = [number, string, boolean];
type promiseTuple = MapToPromise<Tuple>;

let t1: promiseTuple = [
    new Promise((resolve, reject) => {resolve(1)}),     // 1 對應T[K]
    new Promise((resolve, reject) => {resolve('1')}),   // '1' 對應T[K]
    new Promise((resolve, reject) => {resolve(true)})   // true 對應T[K]
];  

console.log(t1);

image.png

unknown類型

一、任何類型均可以賦值給unknown類型

// 一、任何類型均可以賦值給unknown類型
let value2: unknown;
value2 = 123;
value2 = 'abcd';

二、若是沒有類型斷言或基於控制流的類型細化時,unknown不能夠賦值給其餘類型,此時它只能賦值給unknown和any類型

let value11: unknown;
let value2: unknown;
value11 = value2;

三、若是沒有類型斷言或基於控制流的類型細化時,不能進行任何操做

let value4: unknown;
// value4 += 1; // error

四、unknown與任何其餘類型組成的交叉類型,最後都等於其餘類型

type type1 = string & unknown;  // string
type type2 = unknown & unknown;     // unknown

五、unknown與任何其餘類型(除了any)組成的聯合類型,最後都等於unknown類型

type type5 = unknown | string; // unknown

type type6 = any | unknown; // any

type type7 = unknown | number[]  // unknown

六、never類型是unknown類型的子類型

type type8 = never extends unknown ? true : false;  // true

七、keyof unknown 等於類型never

type type9 = keyof unknown;     // never

八、只能對unknown作等或不等的操做,不能進行運算等其餘操做

let value11: unknown;
let value2: unknown;

value11 !== value2
value11 === value2

// value11 += value2;   // error

九、unknown類型的值不能訪問它的屬性、做爲函數調用和做爲類建立實例

let value: unknown;

// value.aa     // error
// value()      // error
// new value()      // error

十、使用映射類型時,若是遍歷的是unknown類型,則不會映射任何屬性

type Type11<T> = {
    [P in keyof T]: number
}

type type11 = Type11<any>;  // { [x: string]: number }
type type12 = Type11<unknown>;  // {}

條件類型

基礎

形如:

T extends U ? x : y

條件類型舉例。

type Type2<T> = T extends string ? string : number;

let index: Type2<'a'> // let index: string

let index2: Type2<1> // let index2: number

let index3: Type2<false> // let index3: number
type TypeName<T> = T extends any ? T : never;

type type13 = TypeName<string | number>;    // string | number

分佈式條件類型

type TypeList<T> =
    T extends string ? string :
    T extends number ? number :
    T extends boolean ? boolean :
    T extends undefined ? undefined :
    T extends () => void ? () => void  :
    object

type Type14 = TypeList<number[]>    // object
type Type15 = TypeList<() => void>     // () => void
type Type16 = TypeList<string>      // string
type Diff<T, U> = T extends U ? never : T;

type Type17 = Diff<string | number | boolean, undefined | number>;      // string | boolean
type Type18<T> = {
    [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T]      // keyof不返回never類型的屬性名,除了函數,其餘都是never

interface Part {
    id: number,
    name: string,
    subparts: Part[],
    updatePart(name: string): void
}

type Type19 = Type18<Part>  // updatePart

image.png

條件類型的類型推斷-infer

type Type20<T> = T extends any[] ? T[number] : T;   // T[number]能夠看做是返回每一個元素對應的類型

type Type21 = Type20<string[]>     // string

type Type21 = Type20<(string | number)[]>   // string | number

type Type22 = Type20<number>    // number
type Type20<T> = T extends any[] ? T[number] : T;

// 等價於使用infer進行的推斷
type Type23<T> = T extends Array<infer U> ? U : T;

ts預約義內置條件類型

Exclude<T, U>
type Etype1 = Exclude<'a' | 'b' | 'c', 'c'>

image.png

Extract<T, U>
type Etype2 = Extract<'a' | 'b' | 'c', 'c'>

image.png

NonNullable<T>
type Ntype1  = NonNullable<string | number | boolean | null | undefined>;

image.png

ReturnType<T>
type Rtype1 = ReturnType<() =>  string>     // string
type Rtype1 = ReturnType<() =>  void>     // void

image.png

image.png

InstanceType<T>

T須要是一個構造函數或者是never、any類型。

type Itype1 = InstanceType<typeof Iclass> // 須要使用typeof標識這個Iclass是個類而不是一個值

type Itype2 = InstanceType<any>

type Itype3 = InstanceType<never>

image.png

相關文章
相關標籤/搜索