添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

TypeScript笔记

基础类型

unknown类型

unknown 类型用于解决any类型使用过于宽松的问题, unknown可以赋任何值,但是unknown不能赋值给其他参数(any和unknown除外)
any类型甚至可以是function,但是unknown类型在赋值之前无法当做方法或者数组来使用

tuple元祖

TS的数据要求是同种类型的值组成,当需要不同类型的值组成组数时,就需要用到tuple
元祖需要在申明变量时就固定值类型与数量比如:

1
2
// 不能不符合类型,也不能多不能少
const tupleValue: [string, boolean, number] = ['abc', false, 123];

object,Object,{}的区别

object是一个数据类型,可用于声明变量,但是微软现在不建议这样使用,而是建议使用 Record<string, unknown>

Object 是所有 Object 类的实例类型,与JS的Object使用类似

{} 是一个没有成员的对象,是一个具体的对象数据,不是一个类型

Never 类型

never 表示那些用不存在的值的类型,常用于断言或者保证方法逻辑性
使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码

TypeScript 断言

可以在变量的前面加上 ,或者在变量后加上 as type,强调变量的类型.
如果想变量不为 undefined 或者 null,可以在赋值的时候后面加上!,例如:

1
2
3
4
function myFunc(maybeString: string | undefined | null) {
const onlyString: string = maybeString; // Error 不能将类型“string | null | undefined”分配给类型“string”。不能将类型“undefined”分配给类型“string”。ts(2322)
const ignoreUndefinedAndNull: string = maybeString!; // Ok
}

交叉类型

TS 中可以通过&符号将 2 个类型合并成一个新类型

1
2
3
4
5
6
7
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

let point: Point = {
x: 1,
y: 1
}

但是如果有同名变量但是类型(基础类型)不一致时,该变量类型会变成 never
如果是非基础类型,例如重新构建的对象类型,就可以同时接受多种类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }

interface A { x: D; }
interface B { x: E; }
interface C { x: F; }

type ABC = A & B & C;

// 此时x 可以接受 boolean,string,number三种类型
let abc: ABC = {
x: {
d: true,
e: 'semlinker',
f: 666
}
};

console.log('abc:', abc);

TypeScript 函数

与 JavaScript 的区别

ts 函数参数需要声明类型,函数的返回值类型也需要声明,可选参数也需要声明

1
2
3
4
// 这里声明了age 是可选参数,性别的默认值是女性,函数无返回值
// 可选参数一般放在最后位置
function foo(name: string, gender: string = 'female, age?: number): void {
}

有了严格的方法参数定义后,TS 就有了函数重载

1
2
3
4
5
6
7
8
9
10
11
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
// type Combinable = string | number;
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString();
}
return a + b;
}

TypeScript 接口

TS 比 JS 更加的面向对象,它是支持接口 interface 的.

1
2
3
4
5
6
7
8
9
interface Person {
name: string;
age: number;
}

let semlinker: Person = {
name: "semlinker",
age: 33,
};

也可以在声明接口的时候设定变量为可读(readonly),同时也可以设置可选(!)变量

interface 与 type 的区别

两者都是用来描述对象的形状或者函数签名,与接口类型不一样,类型别名可以用于一些其他类型,比如原始类型、联合类型和元组,interface 主要用于描述对象和函数

1
2
3
4
5
6
7
8
9
10
11
12
// primitive
type Name = string;

// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };

// union
type PartialPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];

与类型别名不同,接口可以定义多次,会被自动合并为单个接口。

1
2
3
4
interface Point { x: number; }
interface Point { y: number; }

const point: Point = { x: 1, y: 2 };

继承extends与实现Implements

extends

TS 对象的继承就类似于& 交叉合并,但是 extends 可读性更好

1
2
3
4
5
6
7
interface PartialPointX { x: number; }
interface Point extends PartialPointX {
y: number;
};
// 等价于
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

不过一般情况下extends更加倾向于一个对象或者方法继承了父类,是面向对象的一种理念,而不是单纯的交叉合并.

Implements

实现 interface,但类不能实现使用类型别名定义的联合类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Point {
x: number;
y: number;
}

class SomePoint implements Point { // OK
x = 1;
y = 2;
}


type PartialPoint = { x: number; } | { y: number; };
// A class can only implement an object type or
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint { // Error
x = 1;
y = 2;
}

TypeScript 的面向对象

首先是支持静态变量和静态方法 static
静态就是所有的实例对象都公共的,例如:

1
2
3
4
5
class Person {
static time: number;
name: string
};
// 这里所有的人类实例都共享一个共同的 time 变量,但是 name 变量就是每个实例自己的,方法也是同理,而且静态变量和静态方法一般不要改动

11.2 ECMAScript 私有字段

在 TypeScript 3.8 版本就开始支持ECMAScript 私有字段,使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person {
#name: string;

constructor(name: string) {
this.#name = name;
}

greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}

let semlinker = new Person("Semlinker");

semlinker.#name; // ERROR
// Property #name' is not accessible outside class 'Person'
// because it has a private identifier.

与常规属性(甚至使用 private 修饰符声明的属性)不同,私有字段要牢记以下规则:

  • 私有字段以 # 字符开头,有时我们称之为私有名称;
  • 每个私有字段名称都唯一地限定于其包含的类;
  • 不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);
  • 私有字段不能在包含的类之外访问,甚至不能被检测到。
  • private 与# 的区别

    私有变量可以使用 getter 和 setter 方法来访问或者修改私有变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    let passcode = "Hello TypeScript";

    class Employee {
    private _fullName: string;

    get fullName(): string {
    return this._fullName;
    }

    set fullName(newName: string) {
    if (passcode && passcode == "Hello TypeScript") {
    this._fullName = newName;
    } else {
    console.log("Error: Unauthorized update of employee!");
    }
    }
    }

    let employee = new Employee();
    employee.fullName = "Semlinker";
    if (employee.fullName) {
    console.log(employee.fullName);
    }

    TypeScript 支持抽象 abstract

    使用 abstract 关键字声明的类,我们称之为抽象类。抽象类不能被实例化,因为它里面包含一个或多个抽象方法。所谓的抽象方法,是指不包含具体实现的方法

    1
    2
    3
    4
    5
    6
    7
    8
    abstract class Person {
    constructor(public name: string){}

    abstract say(words: string) :void;
    }

    // Cannot create an instance of an abstract class.(2511)
    const lolo = new Person(); // Error

    抽象类不能被直接实例化,我们只能实例化实现了所有抽象方法的子类。具体如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    abstract class Person {
    constructor(public name: string){}

    // 抽象方法
    abstract say(words: string) :void;
    }

    class Developer extends Person {
    constructor(name: string) {
    super(name);
    }

    say(words: string): void {
    console.log(`${this.name} says ${words}`);
    }
    }

    const lolo = new Developer("lolo");
    lolo.say("I love ts!"); // lolo says I love ts!

    TypeScript 类方法支持重载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class ProductService {
    getProducts(): void;
    getProducts(id: number): void;
    getProducts(id?: number) {
    if(typeof id === 'number') {
    console.log(`获取id为 ${id} 的产品信息`);
    } else {
    console.log(`获取所有的产品信息`);
    }
    }
    }

    const productService = new ProductService();
    productService.getProducts(666); // 获取id为 666 的产品信息
    productService.getProducts(); // 获取所有的产品信息

    泛型语法

    泛型顾名思义就是广泛的类型,不特指某种类型.语法上表现为 .例如:

    1
    2
    3
    4
    5
    6
    function identity <T, U>(value: T, message: U) : T {
    console.log(message);
    return value;
    }

    console.log(identity<Number, string>(68, "Semlinker"));

    上面代码中的<T, U> 其实只是一个占位符,或者说是一个类型参数,当调用 identity 方法时,传入该位置的<Number, string>就取代了方法中 T 和 U 的位置,也就是说identity 方法就变成了:

    1
    2
    3
    4
    function identity <Number, string>(value: Number, message: string) : Number {
    console.log(message);
    return value;
    }

    因为泛型只是个占位符,所以定义的时候用什么符号其实都可以,你也可以用 <A><B><C> , 但是使用上习惯用 <T> . 表示 Type
    除了 T 之外,以下是常见泛型变量代表的意思:

  • K(Key):表示对象中的键类型;
  • V(Value):表示对象中的值类型;
  • E(Element):表示元素类型。
  • 泛型可以使用在接口,类,方法.

    装饰器

    装饰器是什么?

    首先看代码例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function Greeter(greeting: string) {
    return function (target: Function) {
    target.prototype.greet = function (): void {
    console.log(greeting);
    };
    };
    }

    @Greeter("Hello TS!")
    class Greeting {
    constructor() {
    // 内部实现
    }
    }

    let myGreeting = new Greeting();
    (myGreeting as any).greet(); // console output: 'Hello TS!';


    // 这里的@Greeter 就类似于当前的 Greeting 类继承了@Greeter

    需要注意的是,若要启用实验性的装饰器特性,你必须在命令行或 tsconfig.json 里启用 experimentalDecorators 编译器选项:

    1
    tsc --target ES5 --experimentalDecorators

    tsconfig.json

    1
    2
    3
    4
    5
    6
    {
    "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
    }
    }

    装饰器分类

    装饰器大致上可以分为以下几种:

  • 属性装饰器
  • 方法装饰器
  • 方法参数装饰器
  • 类装饰器

    对类使用,可传入一个隐式参数作为类的构造参数,或者不传
    但是默认会将被装饰的类做为参数传入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function logClz(注意:参数装饰器只能用来监视一个方法的参数是否被传入;:any) {
    params.prototype.url = 'xxxx';
    params.prototype.run = function() {
    console.log('run...');
    };
    }
    @logClz // 这里将 HttpClient 类传到 logClz
    class HttpClient {
    constructor() { }
    }
    var http:any = new HttpClient();
    http.run(); // run...
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function logClz(params:string) {
    console.log('params:', params); //params: hello
    return function(target:any) {
    console.log('target:', target); //target: class HttpClient
    target.prototype.url = params; //扩展一个url属性
    }
    }
    // 传入参数,target 就是 HttpClient
    @logClz('hello')
    class HttpClient {
    constructor() { }
    }
    var http:any = new HttpClient();
    console.log(http.url); //hello

    属性装饰器

    对类的属性装饰的叫属性装饰器,有 2 个隐式参数:

  • 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象;其实就是当前类
  • 属性成员名
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function logProp(params:any) {
    return function(target:any, attr:any) {
    console.log(target) // { constructor:f, getData:f } (HttpClient)
    console.log(attr) // url
    target[attr] = params; //通过原型对象修改属性值 = 装饰器传入的参数
    target.api = 'xxxxx'; //扩展属性
    target.run = function() { //扩展方法
    console.log('run...');
    }
    }
    }
    class HttpClient {
    @logProp('http://baidu.com') // target: HttpClient, attr: url
    public url:any|undefined;
    constructor() { }
    getData() {
    console.log(this.url);
    }
    }
    var http:any = new HttpClient();
    http.getData(); // http://baidu.com
    console.log(http.api); // xxxxx
    http.run(); // run...

    方法装饰器