再话TypeScript

170 阅读21分钟

快速上手请移步至:TS从入门到出门

TypeScript

数据类型

  • 凡是可以使用父类型的地方都可以使用子类型,反之不行。

基本数据类型

string、number、boolean、null、undefined、bigint、symbol

  • number与bigint类型不兼容。
let name: string = 'AAA';

let age: number = 9;

let isBoy: boolean = true;

let n: null = null;

let u: undefined = undefined;

let x: bigint = 123n;
  • undefined和null是所有类型的子类型

数组

  • 数组成员可以动态变化。
  • 如果没有声明数组类型,会自动推断类型。
  • const声明的数组成员可以改变,如果不允许修改可设置为只读readonly。
  • 使用泛型的数组无法直接使用readonly,有两个专门的泛型用来生成只读数组。
  • readonly只允许用在数组和元组类型的参数前面。
let arr1: string[] = ['a', 'b', 'c'];
let arr3: (number | string)[] = [1, 'A'];
let arr2: Array<string> = ['a', 'b', 'c'];
let arr4: Array<number | string> = [1, 'A'];

//number表示最底层的数组成员类型是number
let arr: number[][] = [[1, 2, 3],[4, 5, 6]];

//arr5: (number | strting)
let arr5 = [1, 2, 3, 'AAA'];

const arr6: number[] = [1, 2, 3];
arr6[0] = 0;

const arr7: readonly number[] = [1, 2, 3];

//报错
const arr8: readonly Array<number> = [1, 2, 3];

const arr9: ReadonlyArray<number> = [1, 2, 3];
const arr10: Readonly<number[]> = [1, 2, 3];
const arr11 = [1, 2, 3] as const;

symbol

  • unique symbol表示单个的、某个具体的Symbol值,使用const声明。
let x: symbol = Symbol();
let y: symbol = Symbol();
x === y;//false

//以下等价
const x: unique symbol = Symbol();
const x = Symbol();

特殊数据类型

any、unknown、never、enum、tuple

any

  • any类型(顶层类型)没有任何限制,可以赋予任意类型值。
  • TypeScript如果无法推断出类型,就会认为该变量是any。
  • any类型有可能会“污染”其他变量。
let x: any = 'This can be any type';
x = 'AAA';
x = 9;
x = true;

//污染 不会报错
let x: any = 'AAA';
let y: number;
y = x;
y * 123;
y.toFixed()

unknown

  • unknown类型(顶层类型)可以解决any导致的“污染”问题。
  • unknown类型不能直接赋值给其他类型的变量。
  • unknown类型不能调用属性和方法。
  • unknown类型只能进行比较运算、取反、typeof、instance of。
  • 确定unknown的类型之后可以进行调用与其他运算。
let x: unknown = 'This can be any type too';
x = 'AAA';
x = 9;
x = true;

//防止污染 会报错
let x: unknown = 'AAA';
let y1: number = x;  //报错
let y2: boolean = x; //报错

let x: unknown = {name:'AAA'};
x.name; //报错

let x: unknown = 9;
x + 1; //报错
x === 9; //true

let x: unknown = 9;
if (typeof x === 'number'){
    let ten = x + 1;
}

let x: unknown = 'AAA';
if (typeof x === 'string'){
    let len = x.length;
}

never

  • never类型为空,永远不会发生的值的类型。
  • never类型(底层类型)可以赋值给任意类型。
function throwError (message: string): never {
    throw new Error(message);
}

function fn(x: string | number) {
    if (typeof x === 'string'){
        
    }else if (typeof x === 'number') {
    
    }else {
        x;
    }
}

enum

TypeScript新增数据类型枚举

  • enum既是一种类型也是一个值,编译后会变成对象留在代码中。
  • 所有成员值均为只读。
  • 同名enum会合并,只允许一个的首成员省略初值,且不能有同名成员
  • 枚举可以正向引用也可以反向引用。
  • 适用于成员的值不重要,名字更重要的场景。
  • 使用时考虑用断言替代。
  • Keyof运算符可以取出Enum结构的所有成员名,作为联合类型返回。
enum Color {
    Red,    //0
    Green,    //1
    Blue    //2
}
let c = Color.Green; //1
let c = Color['Green'] //1
let c: Color = Color.Green;
let c: number = Color.Green;

//赋值
const enum Color {
    Red,    //0
    Green,    //1
    Blue    //2
}
const enum Color {
    Red = 3,    //3
    Green = 6,    //6
    Blue = 9    //9
}
const enum Color {
    Red = 9,    //9
    Green = 9,    //9
    Blue = 9    //9
}
const enum Color {
    Red = 9,    //9
    Green,    //10
    Blue    //11
}
const enum Color {
    Red,    //0
    Green = 9,    //9
    Blue    //10
}

//同名enum会合并,只允许一个的首成员省略初值,且不能有同名成员
const enum Color {
    Red, 
}
const enum Color {
    Green = 9, 
}
const enum Color {
    Blue = 10, 
}
const enum Color {
    Red,    
    Green = 9,    
    Blue = 10    
}

//成员的值可以设为字符串,但是未设置类型的成员,默认为数值且必须位于字符串成员之前
const enum Color {
    Red,    
    Green = 'AAA',    
    Blue = 10    
}

const enum Color {
    Red = 'AAA',
    Blue = 'BBB'
}
//'Red' | 'Blue'
type Foo = keyof typeof Color; 

//反向取值
enum Color {
    Red,
    Blue
}
console.log(Color[1]); //Blue

tuple

  • 成员类型可以自由设置的数组。
  • 必须明确声明每个成员的类型。
  • 数组的成员类型写在方括号外面,元组的成员类型写在方括号里面。
  • 必须明确给出类型声明,不能省略,否则TypeScript会把一个值自动推断为数组。
  • 类型后加?表示类型可选。
  • 可选成员必须位于必选成员之后。
  • 越界会报错而数组不会。
  • 使用扩展运算符...可以表示不限制成员数量的元组,可以用在元组的任意位置,它后面只能是一个数组或元组,(不建议)失去了元组的意义。
  • 可以添加成员名,但没有实际作用。
  • 只读元组。
const s: [string, string, boolean] = ['A', 'B', true];

let a: [number, number?] = [1];

//成员不限制
type NamedNums = [
    string,
    ...number[]
]
const a: NamedNums = ['A', 1];
const b: NamedNums = ['A', 1, 2];
const c: NamedNums = ['A'];

type t1 = [string, number, ...boolean[]];
type t2 = [string, ...boolean[], number];
type t3 = [...boolean[], string, number];

//添加成员名
type Color = [
    red: number,
    green: number,
    blue: number
];
const c:Color = [255, 255, 255];

//读取成员类型
type Tuple = [string, number];
type Age = Tuple[1];//number

//只读元组
//写法一
type t = readonly [number, string];
//写法二
type t = Readonly<[number, string]>;

值类型

单个值也是一种类型,称为“值类型”。

let x: 'hello';
x = 'hello';//true
x = 'hi';//false

联合数据类型

多种类型组合为新类型。

let x:string|number;
x = 123;//true
x = 'hello';//true

let rainbowColor: '赤' | '橙' | '黄' | '绿' | '青' | '蓝' | '紫'

交叉数据类型

多个类型组成的新类型。任何一个类型必须既满足又满足。

  • 主要用于对象合成。
let x: number&string//never 不可能既是number又是string

let obj: {foo: string} & {bar: string};
obj = {
    foo: 'hello',
    bar: 'world'
}

ES6类型

Map<K,V>

let map1 = new Map();//Key any, value any
let map2 = new Map<string, number>();//Key string, value number

const myMap: Map<boolean,string> = new Map([
  [false, 'no'],
  [true, 'yes'],
]);

async函数

const p:Promise<number> =  /* ... */;

async function fn(): Promise<number> {
    var i = await p;
    return i + 1;
}

类型断言

对于没有声明的值,TypeScript会进行类型推断,使用类型断言可以直接告知编译器是什么类型。

  • 指定unknown类型变量的类型。
  • 必须满足值是类型断言的子类型或者类型断言是值的子类型。
  • 如果没有声明变量类型,let声明的变量会被类型推断为基本类型之一,const声明的变量会被推断为值类型常量。
  • as const 断言后相当于使用const命令声明,使用其断言后,值不能再改变。只能用于字面量,不能用于变量,也不能用于表达式。
  • 可以用于整个对象,也可以用于对象的单个属性。
//写法一
let a: T = <T>b;
//写法二 推荐
let a: T = b as T;


type T = 'a' | 'b' | 'c';
let foo = 'a';
let bar: T = foo as T;

//对象中使用类型断言
const obj: {x: number} = {x: 0, y: 0} //报错
const obj: {x: number} = {x: 0, y: 0} as {x: number}//true
const obj: {x: number} = {x: 0, y: 0} as {x: number, y: number}//true

const value: unknown = 'Hello';
const s1: string = value;//error
const s1: string = value as string;//true

//const使用
let s = 'Hello';
type Greet = | 'Hello' | 'Hi';
function sayHello (greet: Greet) {}
sayHello(s);//error

//方法一
const s = 'Hello';
//方法二
let s = 'Hello' as const;//使用后s的值不可改变
type Greet = | 'Hello' | 'Hi';
function sayHello (greet: Greet) {}
sayHello(s);//true

const v1 = {
    x: 1,
    y: 2
};//{x: number, y: number}
const v2 = {
    x: 1 as const,
    y: 2
};//{x: 1, y: number}
const v3 = {
    x: 1,
    y: 2
} as const;//{readonly x: 1, readonly y: 2}

const a1 = [1, 2, 3];//number[]
const a2 = [1, 2, 3] as const;//readonly [1, 2, 3]

非空断言

保证变量不为空,写法是在变量名后面加!

function f (x?: number|null) {
    validateNumber(x);
    console.log(x!.toFixed());
}
function validateName (e?: number|null) {
    if (typeof e !== 'number')
        throw new Error('not a number');
}

断言函数

一种特殊的函数,用于保证函数参数符合某种类型。

function isString (value: unknown): void {
    if (typeof value !== 'string')
        throw new Error('not a string');
}
function toUpper (x: string|number) {
    isString(x);
    return x.toUpperCase();
}

//新写法
function isString (value: unknown): asserts value is string {
    if (typeof value !== 'string')
        throw new Error('not a string');
}

object

  • 一旦声明了类型,对象赋值时,就不能缺少指定的属性,也不能有多余的属性。
  • 类型声明中的属性不能删除只能修改。
  • 使用?:表示该属性是可选的,如果可选属性未被赋值,返回undefined,但可选属性与undefined不等价。
  • 属性名前加readonly表示只读,只读属性只能在对象初始化期间赋值,如果属性是对象,可以修改改对象属性,但是不能替换改对象。
  • 如果一个对象有两个引用,其中一个变量是可写的,一个变量是只读的,那么从可写变量修改属性,会影响到只读变量。
  • 结构类型原则:只要对象B满足对象A的结构特征,TypeScript就认为对象B兼容对象A的类型。
  • 不能动态添加属性,即使是一个空对象。
const obj: {name: string, age:number} = {name: 'AAA', age: 9}

const obj: {x: number, y: number, add(x: number, y: number): number} = { 
    x: 1,
    y: 1,
    add(x, y) {
        return x + y;
    }
}     

type MyObj = {
    name: string,
    age: number
}
const obj: MyObj = {name: 'AAA', age: 9}

interface MyObj {
    name: string,
    age: number
}
const obj: MyObj = {name: 'AAA', age: 9}

const obj: {name: string, age?: number} = {name: 'AAA'}

const obj: {readonly name: string, age: number} = {name: 'AAA', age: 9}
type Obj = {readonly name: string, readonly age: number}
const obj: Obj = {name: 'AAA', age: 9}

//可写影响只读
interface Person {
    name: string,
    age: number
}
interface ReadonlyPerson {
    readonly name: string;
    readonly age: number
}
let p1: Person = {
    name: 'AAA',
    age: 9
}
let p2: ReadonlyPerson = p1;
p1.age += 1;
p2.age //10

//解构
const {id, name, age}: {
    id: number,
    name: string,
    age: number
} = user;

type A = {
    x: number
}
type B = {
    x: number,
    y: number
}
const B = {
    x: 1,
    y: 1
}
const A: {x: number} = B;

function

  • 函数的类型声明,需要在声明函数时,给出参数的类型和返回值。TypeScript可以推断返回值类型。
  • 函数的实际参数个数,可以少于类型指定的参数个数,但是不能多于。
  • 默认参数写法与JavaScript写法一致。
  • 如果函数的某个参数可以省略,参数名后加问号。
  • 可选参数不能与默认值同时使用。
  • 具有默认值的参数如果不位于参数列表的末尾,调用时不能省略,如果要触发默认值,必须显示传入undefined。
  • readonly只读。
  • 高阶函数:一个函数的返回值还是一个函数,那么前一个函数就称为高阶函数。
//写法一
const hello = function (txt: string) {
    console.log('hello' + txt);
}
//写法二
const hello: (txt: string) => void = function (txt) {
    console.log('hello' + txt);
}
//写法二修
type MyFunc = (txt: string) => void;
const hello: MyFunc = function (txt) {
    console.log('hello' + txt);
}

function add (
    x: number,
    y: number
){
    return x + y;
}
const myAdd: typeof add = function (x, y) {
    return x + y;
}

//函数本身还有属性
let foo: {
    (x: number): void;
    version: string
} = f;

//使用接口
interface myfn {
    (a: number, b: number): number;
}
var add: myfn = (a, b) => a + b;

//默认参数
function createPoint (
    x: number = 0,
    y: number = 0
): [number, number] = {
    return [x, y];
}
createPoint();

//可选参数
function f(x?: number) {
    return x;
}
f();//true
f(9);//true
f(undefined);//true

//error 
function f(x?: number = 0){}

function add (
    x: number = 0,
    y: number
){
    return x + y;
}
add(1) //error
add(undefined,1)//true

//参数解构
function f (
    [x, y]: [number, number]
){}
function sum (
    {a, b, c} = {
        a: number;
        b: number;
        c: number
    }
){console.log(a + b + c)}

//结合type解构
type ABC = {
    a: number;
    b: number;
    c: number
}
function sum({a, b, c}: ABC) {console.log(a + b + c)}

//rest参数
//数组
function joinNumbers (...nums: number[]) {}
//元组
function f (...args: [number, boolean?]) {}
//嵌套
function f (...args: [boolean, ...string[]]) {}

//只读
function arraySum (
    arr: readonly number[];
)

//抛出错误
function throwErr (): void {
    throw new Error ('error');
}
function throwErr (msg: string): never {
    throw new Error (msg);
}

//高阶函数
(someValue: number) => (multiplier: number) => someValue * multiplier;

箭头函数

const repeat = (
    str: string,
    times: number
): string => str.repeat(times);

function hello (
    fn: (a: string) => void;
): void {
    fn('hello')
}

type Person = {name: string}
const people = ['A', 'B', 'C'].map(
    (name):Person => ({name})
)

函数重载

函数重载:有些函数可以接受不同类型或者不同个数的参数,并且根据参数的不同,会有不同的函数行为。

  • 每个类型声明之间,以及类型声明与函数实现的类型之间,不能有冲突。
  • 类型最宽的声明放在最后面,防止覆盖其他类型声明。
//函数重载
function reverse (str: string): string;
function reverse (arr: any[]): any[];
function reverse (
    stringOrArray: string|any[];
): string|any[] {
    if (typeof stringOrArray === 'string') 
        return stringOrArray.split('').reverse().join('');
    else
        return stringOrArray.slice().reverse();
}

function fn (x: boolean): void;
function fn (x: string): void;
function fn (x: number|string){}//error
function fn (x: boolean|string){}//true

//重载声明排序
function f(x:any):number;
function f(x:string): 0|1;
function f(x:any):any {};
const a:0|1 = f('hi'); // error

//对象方法重载
class StringBuilder {
    #data = '';
    add (num: number): this;
    add (bool: boolean): this;
    add (str: string): this;
    add (value: any): this {
         this.#data += String(value);
         return this;
    }
    toString() {
        return this.#data;
    } 
}

构造函数

const d = new Date();

class Animal {
    numLegs: number = 4;
}
type AnimalConstructor = new () => Animal;

function create (c: AnimalConstructor): Animal {
    return new c();
}

const a = create(Animal);

class

  • 类可以在顶层声明也可以在构造函数内部声明。

  • 属性前加readonly表示属性只读,实例对象不可修改,构造函数可以修改。

  • 构造函数不能返回声明值类型。

  • 如果某个属性只有get方法,没有set方法,那么该属性自动成为只读属性。

  • 类允许定义属性索引,如果一个对象同时定义了属性索引和方法,那么前者必须包含后者类型。属性存取器视同属性。

  • interface接口或type别名可以用对象的形式,为class指定一组检查条件。然后类使用implements关键字,表示当前类满足这些外部类型条件的限制。

    • implements只是指定检查条件,如果不满足条件就会报错,并不能代替class自身的类型声明。
    • implements后面如果是类,该类被视为接口。
    • 可以实现多个接口。
  • 类和接口同名,接口会被合并进类。

  • 作为类型使用时,类名只能表示实例的类型,不能表示类的自身类型。

  • 结构类型原则:一个对象只要满足Class的实例结构,就跟该Class属于同一类型。

  • 两个类的实例结构相同,那么这两个类就是兼容的,可以用在对方的使用场合。

  • 类也可以写成泛型。

    • 静态成员不能使用泛型的类型参数。
  • 类前面加关键字abstract 表示该类不能被实例化,只能当做其他类的模版,称为抽象类。

    • 抽象类可以继承其他抽象类。
    • 抽象类属性前面加abstract 表示子类必须给出该方法的实现。
    • 一个子类最多继承一个抽象类。
    • 抽象成员前面不能有private,否则无法再子类中实现该成员。
class User {
    name: string;
    age: number
}
//给出初值,不写类型会自动推断属性类型
class User {
    name: 'A';
    age: 9
}
class User {
    name!: string;
    age!: number
}
//readonly方法一
class User {
    readonly id: number = 1;
}
//readonly方法二
class User {
    readonly id: number;
    constructor () {
        this.id = 1;
    }
}

//函数重载
class Point {
    constructor (x: number, y: string);
    constructor (s: string);
    constructor (xs: number|string, y?: string){
    }
}

//存取器方法
class User {
    _name = 'A';
    
    get name () {
        return this._name;
    }
}
const u = new User();
u.name = 'B';//error

//属性索引
class MyClass {
    [s: string]: boolean | ((s: string) => boolean);
    
    get (s: string) {
        return this[s] as boolean;
    }
}

class MyClass {
    [s: string]: boolean | (() => boolean);
    f () {
        return true;
    }
}

class MyClass {
    [s: string]: boolean;
    get isInstance () {
        return true;
    }
}

//implements
interface Country {
    name: string;
    captial: string
}
type Country {
    name: string;
    captial: string;
}
class MyCountry implements Country{
    name = '';
    captial = '';
    id = 1;
}

//类与接口同名
class A {
    x: number = 1;
}
interface A {
    y: number;
}
let a = new A();
a.y = 10;

//类表示类型
class Point {
    x: number;
    y: number;
    
    constructor (x: number, y: number) {
        this.x = x;
        this.y = y;
    } 
}
//error
function createPoint (
    PointClass: Point, //error
    x: number,
    y: number
){return new PointClass(x, y)}
//true
function createPoint (
    PointClass: typeof Point,//true
)

//结构类型原则
class Foo {
    id!: number;
}
function fn (arg: foo) {}
const bar = {
    id: 9,
    amount: 199
};
fn(bar);//true 

//实例结构相同相互兼容
class Person {
    name: string;
}
class Customer {
    name: string;
}
const cust: Customer = new Person();

//泛型类
class Box<Type> {
    contents: Type;
    constructor(value: Type) {
        this.contents = value;
    }
}
const b: Box<string> = new Box('A');

//抽象类
abstract class A {
    id = 1;
}

class B {
    amount = 100;
}

const b = new B();
b.id;//1
b.count;//100

abstract C extends A{
    
}

类的继承

  • 子类可以用于类型为基类的场合。

  • 子类可以覆盖基类的同名方法。

    • 子类同名方法不能与基类的类型定义相冲突。
  • extends后面的类型是构造函数均可实现继承。

class A {
    greet () {
        console.log('A');
    }
}
class B extends A {
}
const b = new B();
b.greet();//A

const a:A = b;
a.greet();

//覆盖
class B extends A {
    greet(name?: string) {
        if (name === 'undefined') {
            super.greet()
        }else {
            console.log(`hello ${name}`);
        }
    }
}

//error 
class B extends A {
    greet(name: string) {
        console.log(`hello ${name}`);
    }
}

class A {
    protected x: string = '';
    protected y: string = '';
    protected z: string = '';
}
class B extends A {
    public x: string = '';//true
    protected y: string = '';//true
    private z: string = '';//error
}

//extends后是构造函数
class MyArray extends Array<number> {};
class MyError extends Error {};

interface

  • interface可以表示对象的各种语法:

    • 对象属性

    • 对象的属性索引

      • 一个接口中,最多只能定义一个字符串索引一个数值索引。索引会约束该类型中所有名字为字符串的属性。
      • 如果一个接口中同时定义了字符串索引和数值索引,那么数值索引必须服从于字符串索引。
    • 对象方法

    • 函数

    • 构造函数

  • 多个同名接口会合并成一个接口。

    • 同名接口合并时,同一个属性如果有多个类型声明,彼此不能有类型冲突。

    • 同名接口合并时,如果同名方法有不同的类型声明,那么会发生函数重载,且后面的定义比前面的定义具有更高的优先级。

      • 同名方法中,如果有一个参数是字面量类型,字面量类型具有更高的优先级。
  • 与type的区别

    • type能够表示非对象类型,而interface只能表示对象类型(包括数组、函数等)。
    • interface可以继承其他类型,type不支持继承。
    • 同名interface会合并,同名type会报错。
    • interface不能包含属性映射,type可以。
    • this关键字只能用于interface。
    • type可以扩展原始数据类型,interface不行。
    • type无法表示复杂类型(交叉类型和联合类型)。
interface User {
    name: string;
    age: 9
}
const u: User = {
    name: 'A',
    age: 9
}
type A = User['name'];//string

//对象属性
interface User {
    readonly id: number;
    name: string;
    age?: number
}
//对象的属性索引
//字符索引
interface User {
    [prop: string]: number;
}
//数值索引
interface A {
    [prop: number]: string;
}
const obj: A = ['a', 'b', 'c'];
//既有字符索引又有数值索引
interface A {
    [prop: string]: number;
    [prop: number]: string;//error
}
interface B {
    [prop: string]: number;
    [prop: number]: number;
}

//对象的方法
//一
interface A {
    f (x: boolean): string;
}
//二
interface B {
    f: (x: boolean) => string;
}
//三
interface C {
    f: {(x: boolean): string};
}
//四
const f = 'f';
interface A {
    [f] (x: boolean): string;
}

//重载
interface A {
    f(): number;
    f(x: boolean): boolean;
    f(x: string, y: string): string;
}
function MyFunc(): number;
function MyFunc(x: boolean): boolean;
function MyFunc(x: string, y: string): string;
function MyFunc(
    x?: boolean | string,
    y?: string
): number | boolean | string {
    if (x === undefined && y === undefined) return 1;
    if (typeof x === 'boolean' && typeof y === 'boolean') return true;
    if (typeof x === 'string' && typeof y === 'string') return 'hello';
    throw new Error('error'); 
}

const a: A = {
    f: MyFunc
}

//函数
interface Add {
    (x: number, y: number): number;
}
const myAdd: Add = (x, y) => x + y;

//构造函数
interface ErrorConstructor {
    new (message?: string): Error;
}

//同名重载
interface Cloner {
    clone(animal: Animal): Animal;
}
interface Cloner {
    clone(animal: Sheep): Sheep;
}
interface Cloner {
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
}
//等同于
interface Cloner {
    clone(animal: Dog): Dog;
    clone(animal: Cat): Cat;
    clone(aniaml: Sheep): Sheep;
    clone(animal: Animal): Animal;
}

interface继承

  • interface继承interface

    • 子接口与父接口的同名属性必须是类型兼容的,不能有冲突。
    • 多重继承时,如果多个父接口存在同名属性,那么这些同名属性不能有类型冲突。
  • interface继承type

    • type定义的类型不是对象,interface就无法继承。
  • interface继承class

//interface 继承 interface
interface Style {
    color: string;
}
interface Shape {
    name: string;
}
interface Circle extends Style, Shape {
    radius: number;
}

//interface 继承 type
type Country = {
    name: string;
    capital: string;
}
interface CountryWithPop extends Country {
    population: number;
}

//interface 继承 class
class A {
    x: string = '';
    y(): boolean {
        return true;
    }
}
interface B extends A {
    z: number;
}
const b: B {
    x: '',
    y: function(){return true},
    z: 123
}

泛型

  • 带有”类型参数“。类型声明需要的变量,需要在调用时传入具体的参数类型。

  • 类型参数可以设置默认值。

    • 一旦类型参数有默认值,就表示它是可选参数,可选参数必须在必选参数之后。
  • 尽量减少使用,类型参数越少越好。

function getFirst<T> (arr: T[]) {
    return arr[0];
}
getFirst<number>([1, 2, 3]);

function comb<T> (arr1: T[], arr2: T[]): T[] {
    return arr1.concat(arr2);
}
comb<number|string>([1, 2], ['a', 'b']);

//多参数
function map<T, U> (
    arr: T[],
    f: (arg: T) => U
): U[] {
    return arr.map(f);
}
map<string, number>(['1', '2', '3'], (n) => parseInt(n));//[1, 2, 3]

//接口的泛型写法
//写法一
interface Box<Type> {
    contents: Type;
} 
let box: Box<string>


interface Comparator<T> {
    compareTo(value: T): number;
}
class Rectangle implements Comparator <Rectangle> {
    compareTo(value: Rectangle): number {}
}

//写法二
interface Fn {
    <Type>(arg: Type): Type;
}
function id <Type> (arg:Type): Type {
    return arg;
}
let myId: Fn = id;

//类的泛型写法
class Pair <K, V> {
    key: K;
    value: V;
}

const Container = class<T> {
    constructor (private readonly data: T) {}
};
const a = new Container<boolean>(true);
const b = new Container<number>(0);

//类型别名的泛型写法
type Container<T> = {value: T};
const a: Container<number> = {value: 0};
const b: Container<string> = {value: 'A'}

//树形结构
type Tree<T> = {
    value: T;
    left: Tree<T> | null;
    right: Tree<T> | null
}

function getFirst <T = string> (
    arr: T[]
): T {return arr[0]}

namespace

  • namespa用来建立一个容器,内部的所有变量和函数,都必须在这个容器里面使用。

  • 多个同名的namespace会自动合并。

    • 命名空间中的非export的成员不会被合并,但它们只能在各自的命名空间中使用。
  • 命名空间可以与同名函数合并,但同名函数必须声明于命名空间之前。同名的命名空间相当于给函数对象添加额外的属性。

namespace Utils {
    function isString (value: any) {
        return typeof value === 'string';
    }
    
    export function isNumber (value: any) {
        return typeof value === 'number';
    } 
    
    isString('hello');//true
}
Utils.isString('hi');//false 只能在内部使用

Utils.isNumber(9);//true export可以在外部使用

namespace App {
    import isNumber = Utils.isNumber;
    
    isNumber(9);//true
}
namespace App {
    function isBoolean (value: boolean) {
        return typeof value === 'boolean';
    }
    export function sayHello () {
        console.log('Hello');
    }
}

装饰器

  • 一种语法结构,用来在定义时修改类的行为。

  • 特征

    • 前缀为@后面为表达式。
    • @后的表达式必须是一个函数(执行后可以得到一个函数)。
    • 这个函数接受所修饰对象的一些相关值作为参数。
    • 函数要么不返回值,要么返回一个新对象取代所修饰的目标对象。
    • 类执行前会先执行装饰器,并且会向装饰器自动传入参数。
  • 装饰器函数的类型定义

    • value:所修饰的对象。

    • context:上下文对象。

      • kind:所装饰的对象类型。

        • class:类装饰器一般用来对类进行操作,可以不返回任何值。

          • 类装饰器可以返回一个函数,替代当前类的构造方法。
        • method:方法装饰器用来装饰类的方法。

        • getter:针对类的取值器(getter)。

        • setter:针对类的存值器(setter)。

        • field:属性装饰器用来装饰定义在类顶部的属性。

          • 属性装饰器要么不返回值,要么返回一个函数,该函数会自动执行,用来对所装饰属性进行初始化。
        • accessor:为属性x自动生成存取值器和存值器,它们作用于私有属性x。

      • name:字符串或Symbol值,所装饰对象的名字,比如类名、属性名等。

      • addInitializer():函数,用来添加类的初始化逻辑。

      • private:布尔值,表示所装饰的对象是否为类的私有成员。

      • static:布尔值,表示所修饰的对象是否为类的静态成员。

      • access:一个对象,包含某个值的get和set方法。

  • 执行顺序

    • 评估:计算@符号后面的表达式的值,得到的应该是函数。

    • 应用:把评估装饰器后得到的函数应用于所装饰对象。

      • 方法装饰器->属性装饰器->类装饰器
function simpleDecorator ( target: any, context: any) {
    console.log('hi' + target);
    return target;
}
@simpleDecorator
class A {} //hi

//装饰器函数的类型定义
type Decorator = (
    value: DecoratedValue,
    context: {
        kind: string;
        name: string | symbol;
        addInitializer?(initializer: () => void): void;
        static?: boolean;
        private?: boolean;
        access: {
          get?(): unknown;
          set?(value: unknown): void;
        };
     }
) => void | ReplacementValue;

//类装饰器
function Greeter(value, context) {
  if (context.kind === 'class') {
    value.prototype.greet = function () {
      console.log('hello');
    };
  }
}
@Greeter
class User {}
let u = new User();
u.greet(); // "hello"

//方法装饰器
function trace(decoratedMethod) {
  // 
}
// `@trace` 等同于
// C.prototype.toString = trace(C.prototype.toString);
class C {
  @trace
  toString() {
    return 'C';
  }
}

//属性装饰器
function logged(value, context) {
  const { kind, name } = context;
  if (kind === 'field') {
    return function (initialValue) {
      console.log(`initializing ${name} with value ${initialValue}`);
      return initialValue;
    };
  }
}
class Color {
  @logged name = 'green';
}
const color = new Color();
// "initializing name with value green"

//accessor装饰器
class C {
  accessor x = 1;
}
//等同于
class C {
  #x = 1;

  get x() {
    return this.#x;
  }

  set x(val) {
    this.#x = val;
  }
}

类型映射

  • 将一种类型按照映射规则,转换成另一种类型。

  • 映射修饰符会原样复制原始对象的可选属性和只读属性。

      • +? +readonly
      • -? -readonly
  • 键名重映射

    • 属性过滤,可以过滤掉某些属性。
type A = {
    foo: number;
    bar: number;
}
type B = {
    foo: string;
    bar: string;
}
//映射
type B = {
    [Prop in keyof A]: string;
}

type MyObj = {
    [p in string]: boolean;
}
//等同于
type MyObj = {
    [p: string]: number;
}

//将属性改为可选属性
type A = {
    a: string;
    b: number;
};
type B = {
    [Prop in keyof A]?: A[Prop];
}

//将属性改为只读属性
type Readonly <T> = {
    readonly [P in keyof T]: T[P];
}

type A = {
    a?: string;
    readonly b: number;
}
type B = {
    [Prop in keyof A]: A[Prop];
}
//等同于
type B = {
    a?: string;
    readonly b: number;
}

// 添加可选属性
type Optional<Type> = {
  [Prop in keyof Type]+?: Type[Prop];
};

// 移除可选属性
type Concrete<Type> = {
  [Prop in keyof Type]-?: Type[Prop];
};

// 添加 readonly
type CreateImmutable<Type> = {
  +readonly [Prop in keyof Type]: Type[Prop];
};

// 移除 readonly
type CreateMutable<Type> = {
  -readonly [Prop in keyof Type]: Type[Prop];
};

//键名重映射
type A = {
  foo: number;
  bar: number;
};

type B = {
  [p in keyof A as `${p}ID`]: number;
};

// 等同于
type B = {
  fooID: number;
  barID: number;
};

//过滤属性
type User = {
  name: string,
  age: number
}

type Filter<T> = {
  [K in keyof T as T[K] extends string ? K : never]: string
}

type FilteredUser = Filter<User> // { name: string }

declare

declare关键字用来告诉编译器,某个类型是存在的,可以在当前文件中使用,且不用给出具体实现。

declare不会出现在编译后的文件里。

  • 变量(const、let、var 命令声明)
  • type 或者 interface 命令声明的类型。
  • class
  • enum
  • 函数(function)
  • 模块(module)
  • 命名空间(namespace)
//变量
declare let x:number;
x = 1;

//函数
declare function sayHello(
  name:string
):void;
sayHello('张三');
//error
function sayHello(
  name:string
):void;
function sayHello(name) {
  return '你好,' + name;
}

//类
declare class Animal {
  constructor(name:string);
  eat():void;
  sleep():void;
}

//命名空间
declare namespace AnimalLib {
  class Animal {
    constructor(name:string);
    eat():void;
    sleep():void;
  }
  type Animals = 'Fish' | 'Dog';
}

//模块
declare module AnimalLib {
  class Animal {
    constructor(name:string);
    eat(): void;
    sleep(): void;
  }
  type Animals = 'Fish' | 'Dog';
}

//enum
declare enum E1 {
  A,
  B,
}
declare enum E2 {
  A = 0,
  B = 1,
}
declare const enum E3 {
  A,
  B,
}
declare const enum E4 {
  A = 0,
  B = 1,
}

参考:阮一峰《TypeScript教程》