首先要弄清楚 TypeScript 可以做什么。
在一个项目逐渐庞大,各种 API 和函数层出不穷,往往传错一个参数短时间并不会影响什么,但是时间一久就会引发灾难性的问题。
TypeScript 通过在编译时检查其 Type 来避免类似参数传入错误的问题。同时通过限制参数的类型(包括 类、函数)的结构,避免参数乱传的问题。
TypeScript 手册相对于其他语言来说完全算少,尤其是对于我们这种 ES6 已经写了大半年的人来说。
所以更多的是怎么快速的掌握和记住一些 TypeScript 的细节。
看完手册已经差不多一个月了,这次作为复习也作为笔记。
1. 基础类型
支持 const、let
let 变量: type = value
boolean, number, string, number[], Array<number>, [string, number], any, enum
enum Color = { Red: 1, Green, Blue }
Object
void(undefined, null)
1
function warnUser(): void;
undefined, null
never
1
2
3function error(message: string): never {
throw new Error(message);
}declare 声明
1
2
3
4
5declare function create(o: object): void;
function create(o: object) {
return;
}let a = <number>b
let a = b as number
2. 变量声明
let
const
const [a, b]: = [1, 2];
const { a: c, b } = { a: 1, b: 2 };
type C = { a: string, b?: number }
const newList = [ ...oldList ];
const [ a ] = [ 1 ]
3. 接口
interface
1
2
3
4
5
6
7
8
9
10
11interface SquareConfig {
readonly name: string;
color: string;
width: number;
}
const config: SquareConfig = {
name: "Bob",
color: "red",
width: 300
};ReadonlyArray
1
let ro: ReadOnlyArray<number> = a;
interface 和 type 的区别见<其他>
createSquare({ xxx: "xxx" } as SquareConfig)
propName
- 适合在外部传入数据并且在不知道结构的情况下全全保存。
1
2
3interface U {
[propName: string]: any
}函数类型的 interface
1
2
3interface SearchFunc {
(source: string, subString: string): boolean;
}数组类型的 interface
1
2
3interface StringArray {
[index: number]: string;
}索引
- 字符串 or 数字
- 数字的索引返回值的集合 必须是 字符串索引返回值的集合的子集。
- 索引可以限制所有子类型的返回值。 (type or readonly)
类类型
1
2
3
4
5interface C {
new (hour: number: minute?: number);
time: Date;
setTime(d: Date): boolean;
}继承多个接口
1
2
3interface Square extends Shape, PenStroke {
sideLength: number;
}支持 对象和函数混合接口
接口继承类
1
2
3class Button extends Control implements SelectableControl {
select() { }
}
4. 类
public
private
protected
protected
修饰符与private
修饰符的行为很相似,但有一点不同,protected
成员在派生类中仍然可以访问。
readonly
constructor 的参数支持直接赋值为 class 属性;<参数属性>;
1
2
3
4
5
6
7class F {
constructor(public readonly f: string) {
}
}
const f = new F("Bob");
console.log(f.f);get/set 支持<存取器>
1
2
3
4
5
6
7
8
9
10class G {
private _fullName: string;
get fullName() {
return this._fullName;
}
set fullName(name: string) {
this._fullName = name;
}
}static origin = { a: 1, b: 2 };
抽象类
1
2
3
4
5
6
7
8
9abstract class Department {
abstract printMeeting(): void;
}
class AccountingDepartment extends Department {
printMeeting() {
console.log("printf");
}
}把<类>当做<接口>
1
2
3
4
5
6
7class Point {
x: number;
y: number;
}
interface Point3D extends Point {
z: number;
}
5. 函数
基本结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// 1
function done(q: string): string {
return q;
}
// 2
const redone: (q: string) => string = function(q: string): string {
return q;
};
// 3
type Q = {
(q: string): string;
};
const reredone: Q = function(q: string): string {
return q;
};
// 4
const rrrrdone: ({ (q: string): string; }) = function(q: string): string {
return q;
};
// 5
interface P {
(q: string): string;
}
const rrrrrdone: P = function(q: string) {
return q;
}可选参数
1
2
3function buildName(firstName: string, lastName?: string): void {
}支持默认参数
function Q(q = "xxx") {}
剩余参数
1
2
3function buildName(firstName: string, ...restOfName: string[]) {
}支持重载
6. 泛型
demo
1
2
3
4function identity<T>(arg: T): T {
return arg;
}
const output = identity<string>("hello world");multi
1
2
3function id<T, S>(argA: T, argB: S): S {
}interface
1
2
3interface A {
<T>(arg: T): T;
}
7. 枚举
enum Direction { Up=1, Down, Left, Right }
默认从 0 开始
数字枚举 or 字符串枚举。
异构枚举(Heterogeneous enums)
反向映射
1
2
3
4
5
6enum Enum {
A
}
let a = Enum.A;
let nameofA = Enum[a]; // "A"const enum { A, B }
外部枚举
外部枚举是用来引入其他非 ts 代码的外部包已存在的枚举
说白了就是写在 index.d.ts 里
1
2
3
4
5declare enum Enum {
A = 1,
B,
C = 2
}
8. 类型推论
(number|string|boolean)
9. 类型兼容性
兼容性只得是某个对象能否被赋值给另一个对象
属性相同的 class 和 interface 可以兼容。
函数比较的是参数
1
2
3
4
5let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
x = y; // Error可选参数和剩余参数在转换时不会产生错误
类之对比实例成员,不对比静态成员和构造函数
private 和 protect 只允许赋值给父类。
泛型在结构相同的情况下可以转换,但是前提是不包含属性不同的成员。如果两者都是 any 就不会有问题。
10. 高级类型
10.1 交叉类型(Intersection Types)
指的是某个对象拥有多个 type,属于多个 type 的成员。
T & U
检查类型检查的是所拥有的 key。如果吧 A、B 的key 都复制到 C 上面。那么
typeof C == typeof A & typeof B
demo
1
2
3
4
5
6
7
8
9
10
11
12function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
10.2 联合类型(Union Types)
number | string
- 出参如果是联合类型,因为无法确定具体属于什么类型。导致只有两个类型共有部分的属性可以被使用。
- 可以使用强制转换。
10.3 类型保护与区分类型(Type Guards and Differentiating Types)
为了解决 联合类型 输出参数类型不确定问题
类型断言
1
2
3
4let pet = getSmallPet();
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
}用户自定义的类型保护
1
2
3
4
5
6
7function isFish(pet: Fish|Bird): pet is Fish {
return <Fish>pet).swim !== undefined;
}
if (isFish(pet)) {
pet.swim();
}typeof
类型保护- typeof 仅支持
number
,string
,boolean
,symbol
if (typeof xx === 'string') { ... }
- typeof 仅支持
instanceof
类型保护- 此构造函数的
prototype
属性的类型,如果它的类型不为any
的话 - 构造签名所返回的类型的联合
上面两个感觉是机翻,没有例子也看不懂。
- 此构造函数的
可以为 null 的类型
null
与undefined
是所有其它类型的一个有效值。--strictNullChecks
标记可以解决此错误
类型别名
type
不能出现在创建一个<声明>时,右侧有任何的声明
<我感觉一辈子都不会这么去用 orz>
1
2
3
4
5
6
7
8
9
10
11type LinkedList<T> = T & { next: LinkedList<T> };
interface Person {
name: string;
}
var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;type 和 interface 的区别
- type 更像是一个指针,在很多地方【错误】【提示】 等都会显示 type 原始的类型。
- interface 可以被 extends 和 implement 而 type 不行。
字符串字面量类型
type Easing = "ease-in" | "ease-out" | "ease-in-out";
- 字符串字面量类型还能区别与 string 而用于重载。
数字子面型类型
- 同上
- 可以用来调试代码,缩小 number 范围带来的变量的报错。
枚举成员类型
参照 <枚举> 。
可辨识联合(Discriminated Unions)
<标签联合>、<代数数据联合>
demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
default: return assertNever(s); // error here if there are missing cases
}
}
多态的 this 类型
- 菊花链的故事
索引类型(Index Types)
let personProps: keyof Person; // 'name' | 'age'
demo
1
2
3function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
return o[name]; // o[name] is of type T[K]
}
映射类型
可以支持把一个类型的 type 转换成另个类型的 type。
常用的几个类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 把 type 所有类型都转成 ReadOnly
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
// 把 type 所有类型转成可选
type Partial<T> = {
[P in keyof T]?: T[P];
}
// 拆分 type 的若干个类型转换成一个新的 type
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
// 用 T 作为属性转成新的类型
type Record<K extends string, T> = {
[P in K]: T;
}包装 & 拆包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 包装
type Proxy<T> = {
get(): T;
set(value: T): void;
}
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
}
function proxify<T>(o: T): Proxify<T> {
// ... wrap proxies ...
}
let proxyProps = proxify(props);
// 拆包
function unproxify<T>(t: Proxify<T>): T {
let result = {} as T;
for (const k in t) {
result[k] = t[k].get();
}
return result;
}
let originalProps = unproxify(proxyProps);实际包装完整代码(琢磨了半天)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29type Proxy<T> = {
get(): T;
set(value: T): void;
}
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
}
function pro<T>(a: T): Proxy<T> {
const b: Proxy<T> = {
get() {
return a;
},
set(value: T) {
}
}
return b;
}
function proxify<T extends Object>(o: T): Proxify<T> {
const p: Proxify<T> = {} as Proxify<T>;
for(const key in o) {
p[key] = pro(o[key]);
}
return p;
}
let proxyProps = proxify({ a: '2333'});类似
K extends keyof T
。因为 keyof 类似 Object.keys 返回的是复数的属性,而且在<> 也可以传入类似<'aa'|'bb'>
。所以在使用必须用[key in K]
。预定义的有条件类型
TypeScript 2.8在
lib.d.ts
里增加了一些预定义的有条件类型:Exclude<T, U>
– 从T
中剔除可以赋值给U
的类型。Extract<T, U>
– 提取T
中可以赋值给U
的类型。NonNullable<T>
– 从T
中剔除null
和undefined
。ReturnType<T>
– 获取函数返回值类型。InstanceType<T>
– 获取构造函数类型的实例类型。
11. Symbols
let sym1 = Symbol(); let sym2 = Symbol("key");
- 还有很多内置的特性参照:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator
12. 迭代器 & 生成器
for...of
for...in
13. 模块
export
export xx as ‘yy’
export default
import
import * as xx from ‘xx’;
CommonJS 和 AMD 的 exports
export =
import xx require=
使用其他的 JavaScript 库
demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14// index.d.ts
declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}
// main.ts
import url from 'url';
import * as url from 'url';
14. 命名空间
个人理解,命名空间适合前端在编写 ts 时,在一个大文件维持各个 module 之间的关系,并确保变量名不会相互影响。而 Node 在编写代码时使用文件的方式来分隔。
关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与 ECMAScript 2015里的术语保持一致,(也就是说
module X {
相当于现在推荐的写法namespace X {
)。引用 https://www.tslang.cn/docs/handbook/namespaces-and-modules.html
1 | namespace Validation { |
- 多个文件的同个命名空间会把他们合并到一起
/// <reference path="Test.ts" />
要预先引用- 引入原生 js 库。需要提供
.d.ts
文件
15. 命名空间 & 模块
- 内部模块 = namespace + reference
- 外部模块 = export + import
- 拒绝
export namespace XX {}
16. 模块解析
export xx;export yy
=import { xx, yy } from './module'
export xx;export yy;
=import * as zz from './module'; zz.xx; zz.yy;
export default xx;
=import xx from './module'
;module.exports = xx
=import xx = require('module');
17. 声明合并
接口 (interface) 可以合并
空间(namespace) 可以合并
非导出成员仅在其原有的(合并前的)命名空间内可见。
命名空间和类 - 命名空间里的成员必须导出,才能在类里面被调用
全局扩展
1
2
3
4
5declare global {
interface xxx {
}
}
18. JSX
.tsx
19. 装饰器
配置
1
2
3
4
5
6{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}基本格式
1
2
3
4function f() {
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
}
}
20. Mixins
demo
1
2
3
4
5
6
7function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
})
});
}用与混合两个类
class newClass implements oldClassA, oldClassB {}
需要在
newClass
简单的实现需要转移的参数和方法
21. 三斜线指令
- 三斜线指令是包含单个XML标签的单行注释
- ``///
` /// <reference path="..." />
22. JavaScript 文件类型检查
支持用 JSDoc 类型来表示类型信息(js 文件)
支持的 JSDoc
@type
@param
(or@arg
or@argument
)@returns
(or@return
)@typedef
@callback
@template
@class
(or@constructor
)@this
@extends
(or@augments
)@enum
100. Demo
Pick
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16interface a {
a: string;
};
interface b {
a: any
}
interface d extends Pick<a, keyof b> {
c: number;
}
const test: d = {
a: '123',
c: 123
};Omit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15interface a {
a: string;
};
interface b {
a: any
}
interface d extends Omit<a, keyof b> {
a: number;
}
const test: d = {
a: 1
};