跳到主要内容

重点

映射修饰符(Mapping Modifiers)和 - + 符合

在使用映射类型时,有两个额外的修饰符可能会用到,一个是 readonly,用于设置属性只读,一个是 ? ,用于设置属性可选。

你可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀。

// 删除属性中的只读属性
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};

type LockedAccount = {
readonly id: string;
readonly name: string;
};

type UnlockedAccount = CreateMutable<LockedAccount>;

// type UnlockedAccount = {
// id: string;
// name: string;
// }
// 删除属性中的可选属性
type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};

type MaybeUser = {
id: string;
name?: string;
age?: number;
};

type User = Concrete<MaybeUser>;
// type User = {
// id: string;
// name: string;
// age: number;
// }

typescript class可以作为类型定义被扩展

// 定义一个class类
class Poin2 {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}

// 作为类型使用
const test1:Poin2 = {
x: 1,
y: 2
}

// 被接口扩展
interface Point3d extends Poin2 {
z: number;
}

const btn: Point3d = {
x: 1,
y: 2,
z: 3,
};

// 被类型别名扩展
type fhsadjk = {
z: string;
} & Poin2;

const btn1: fhsadjk = {
z: '123',
x:1,
y: 2
}

参数属性

参数属性可以方便地让我们在一个地方定义并初始化一个成员。 使用了参数属性:

class Info {
constructor(
public name: string
private age: number ) {}
}

它可能看上去不像是有属性的类,但它确实有,利用的是 Typescript 提供的简写形式 — 用构造函数的参数直接定义属性。

参数属性通过给构造函数参数添加一个访问限定符来声明。 使用private限定一个参数属性会声明并初始化一个私有成员;对于public和protected来说也是一样。

  • 声明了一个构造函数参数及其类型
  • 声明了一个同名的公共属性
  • 当我们 new 出该类的一个实例时,把该属性初始化为相应的参数值

索引访问操作符

使用 [] 操作符可以进行索引访问

interface Person {
name: string;
age: number;
}

type x = Person["name"]; // x is string

//批量把一个接口中的属性都变成可选的
type PartPerson = {
[Key in keyof Person]?: Person[Key];
};


当value为never时,就会访问不到

type person = { 
name:"Angus";
height:185;
girlFriend:never;
}["name" | "height" | "girlFriend"]
// type person = "Angus" | 185

object、Object 和 {}

{}、大 Object 是比小 object 更宽泛的类型(least specific),{} 和大 Object 可以互相代替,用来表示原始类型(null、undefined 除外)和非原始类型;而小 object 则表示非原始类型。

确定赋值断言

在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值

let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
x = 10;
}

字符串字面量类型

type Direction = 'up' | 'down';

只读属性

一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用 readonly来指定只读属性 TypeScript具有ReadonlyArray<T>类型,它与Array<T>相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改:

interface Point {
readonly x: number;
readonly y: number;
}

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

接口与类

  • 一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,
  • 这时候就可以把特性提取成接口(interfaces),
  • 用 implements 关键字来实现。
  • 这个特性大大提高了面向对象的灵活性。
  • 一个类可以实现多个接口
  • 接口可以继承接口
  • 接口也可以继承继承类

泛型约束

interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}

多重泛型

class Pair<K,V>{
key:k;
value:V;
}

keyof

keyof 与 Object.keys 略有相似,只不过 keyof 取 interface 的键。

interface Point {
x: number;
y: number;
}

// type keys = "x" | "y"
type keys = keyof Point;

Partial & Pick

type Partial<T> = {
[P in keyof T]?: T[P];
};

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

interface User {
id: number;
age: number;
name: string;
};

// 相当于: type PartialUser = { id?: number; age?: number; name?: string; }
type PartialUser = Partial<User>

// 相当于: type PickUser = { id: number; age: number; }
type PickUser = Pick<User, "id" | "age">

Condition Type

类似于 js 中的 ?: 运算符,可以使用它扩展一些基本类型

T extends U ? X : Y

type isTrue<T> = T extends true ? true : false
// 相当于 type t = false
type t = isTrue<number>

// 相当于 type t = false
type t1 = isTrue<false>

Never

never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}

never & Exclude & Omit

结合 never 与 conditional type 可以推出很多有意思而且实用的类型,比如 Omit

type Exclude<T, U> = T extends U ? never : T;

// 相当于: type A = 'a'
type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>

结合 Exclude 可以推出 Omit 的写法

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

interface User {
id: number;
age: number;
name: string;
};

// 相当于: type PickUser = { age: number; name: string; }
type OmitUser = Omit<User, "id">

typeof

官方:"类型别名 可以和interface关键字一样,然而他们有一些细微的差别。" 顾名思义,typeof 代表取某个值的 type,可以从以下示例来展示他们的用法

const a: number = 3

// 相当于: const b: number = 4
const b: typeof a = 4

type 和 interface

interface是接口,type是类型(type aliases 类型别名),本身就是两个概念。只是碰巧表现上比较相似。

希望定义一个变量类型,就用type,如果希望是能够继承并约束的,就用interface。 如果你不知道该用哪个,说明你只是想定义一个类型而非接口,所以应该用type。

相同点

都可以描述一个对象或者函数 都允许拓展(extends):

  • interface 可以 extends, 但 type 是不允许 extends 和 implement 的
  • 但是 type 缺可以通过交叉类型 实现 interface 的 extend 行为,
  • 并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 与 interface 类型 交叉 。

type extends type


type Name = {
name: string;
}
type User = Name & { age: number };

interface extends type


type Name = {
name: string;
}
interface User extends Name {
age: number;
}

type extends interface


interface Name {
name: string;
}
type User = Name & {
age: number;
}

type 可以而 interface 不行

  • type 可以声明基本类型别名,联合类型,元组等类型
  • type 语句中还可以使用 typeof 获取实例的 类型进行赋值

// 基本类型别名
type Name = string

// 联合类型
interface Dog {
wong();
}
interface Cat {
miao();
}

type Pet = Dog | Cat

// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]

// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div

interface 可以而 type 不行

interface 能够声明合并

interface User {
name: string
age: number
}

interface User {
sex: string
}

/*
User 接口为 {
name: string
age: number
sex: string
}
*/

type 特性

声明一个非空的类型

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

创建一颗tree

type Tree<T> = T & { parent: Tree<T> };

三斜杠指令

习惯上,常常把外部声明写在一个后缀名为 .d.ts 的声明文件中,然后用三斜线指令引入进来

// jquery.d.ts 文件
declare let $: (selector: string) => {
html: (content: string) => void;
};

// main.ts 文件
/// <reference path="./jquery.d.ts" />
$('body').html('hello world');

main.ts 依赖 jquery.d.ts 声明文件,在编译阶段,被依赖文件 jquery.d.ts 将被包含进来,就像将被依赖文件的源码展开在依赖声明处一样:

// main.ts文件等价于将代码在三斜线指令处展开
declare let $: (selector: string) => {
html: (content: string) => void;
};
$('body').html('hello world');

三斜线指令中需要注意的是 path 类型和 types 类型的区别:

/// <reference path="./jquery.d.ts" />
/// <reference types="node" />
  • path 类型声明的是对本地文件的依赖,包含路径信息
  • types 类型声明的是对 node_modules/@types 文件夹下的类型的依赖,不包含路径信息,被依赖文件 node.d.ts 将被包含进来,就像将被依赖文件的源码展开在依赖声明处一样

命名空间的原理

关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与ECMAScript 2015里的术语保持一致,(也就是说 module X { 相当于现在推荐的写法 namespace X {)。

还是通过函数来隔离外部环境的

namespace Tools {
const TIMEOUT = 100;

export class Ftp {
constructor() {
setTimeout(() => {
console.log('Ftp');
}, TIMEOUT)
}
}

export class Http {
constructor() {
console.log('Http');
}
}

export function parseURL(){
console.log('parseURL');
}
}

// 外部访问
Tools.TIMEOUT // 报错, Tools上没有这个属性
Tools.parseURL() // 'parseURL'


编译成JS

"use strict";
var Tools;
(function (Tools) {
const TIMEOUT = 100;
class Ftp {
constructor() {
setTimeout(() => {
console.log('Ftp');
}, TIMEOUT);
}
}
Tools.Ftp = Ftp;
class Http {
constructor() {
console.log('Http');
}
}
Tools.Http = Http;
function parseURL() {
console.log('parseURL');
}
Tools.parseURL = parseURL;
})(Tools || (Tools = {}));