Maple's Blog.

TypeScript 学习笔记

字数统计: 3.3k阅读时长: 15 min
2018/07/03

首先要弄清楚 TypeScript 可以做什么。

在一个项目逐渐庞大,各种 API 和函数层出不穷,往往传错一个参数短时间并不会影响什么,但是时间一久就会引发灾难性的问题。

TypeScript 通过在编译时检查其 Type 来避免类似参数传入错误的问题。同时通过限制参数的类型(包括 类、函数)的结构,避免参数乱传的问题。

TypeScript 手册相对于其他语言来说完全算少,尤其是对于我们这种 ES6 已经写了大半年的人来说。

所以更多的是怎么快速的掌握和记住一些 TypeScript 的细节。

看完手册已经差不多一个月了,这次作为复习也作为笔记。

1. 基础类型

  1. 支持 const、let

  2. let 变量: type = value

  3. boolean, number, string, number[], Array<number>, [string, number], any, enum

  4. enum Color = { Red: 1, Green, Blue }

  5. Object

  6. void(undefined, null)

    1
    function warnUser(): void;
  7. undefined, null

  8. never

    1
    2
    3
    function error(message: string): never {
    throw new Error(message);
    }
  9. declare 声明

    1
    2
    3
    4
    5
    declare function create(o: object): void;

    function create(o: object) {
    return;
    }
  10. let a = <number>b

  11. let a = b as number

2. 变量声明

  1. let
  2. const
  3. const [a, b]: = [1, 2];
  4. const { a: c, b } = { a: 1, b: 2 };
  5. type C = { a: string, b?: number }
  6. const newList = [ ...oldList ];
  7. const [ a ] = [ 1 ]

3. 接口

  1. interface

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface SquareConfig {
    readonly name: string;
    color: string;
    width: number;
    }

    const config: SquareConfig = {
    name: "Bob",
    color: "red",
    width: 300
    };
  2. ReadonlyArray

    1
    let ro: ReadOnlyArray<number> = a;
  3. interface 和 type 的区别见<其他>

  4. createSquare({ xxx: "xxx" } as SquareConfig)

  5. propName

    • 适合在外部传入数据并且在不知道结构的情况下全全保存。
    1
    2
    3
    interface U {
    [propName: string]: any
    }
  6. 函数类型的 interface

    1
    2
    3
    interface SearchFunc {
    (source: string, subString: string): boolean;
    }
  7. 数组类型的 interface

    1
    2
    3
    interface StringArray {
    [index: number]: string;
    }
  8. 索引

    1. 字符串 or 数字
    2. 数字的索引返回值的集合 必须是 字符串索引返回值的集合的子集。
    3. 索引可以限制所有子类型的返回值。 (type or readonly)
  9. 类类型

    1
    2
    3
    4
    5
    interface C {
    new (hour: number: minute?: number);
    time: Date;
    setTime(d: Date): boolean;
    }
  10. 继承多个接口

    1
    2
    3
    interface Square extends Shape, PenStroke {
    sideLength: number;
    }
  11. 支持 对象和函数混合接口

  12. 接口继承类

    1
    2
    3
    class Button extends Control implements SelectableControl {
    select() { }
    }

4. 类

  1. public

  2. private

  3. protected

    1. protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问。
  4. readonly

  5. constructor 的参数支持直接赋值为 class 属性;<参数属性>;

    1
    2
    3
    4
    5
    6
    7
    class F {
    constructor(public readonly f: string) {

    }
    }
    const f = new F("Bob");
    console.log(f.f);
  6. get/set 支持<存取器>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class G {
    private _fullName: string;
    get fullName() {
    return this._fullName;
    }

    set fullName(name: string) {
    this._fullName = name;
    }
    }
  7. static origin = { a: 1, b: 2 };

  8. 抽象类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    abstract class Department {
    abstract printMeeting(): void;
    }

    class AccountingDepartment extends Department {
    printMeeting() {
    console.log("printf");
    }
    }
  9. 把<类>当做<接口>

    1
    2
    3
    4
    5
    6
    7
    class Point {
    x: number;
    y: number;
    }
    interface Point3D extends Point {
    z: number;
    }

5. 函数

  1. 基本结构

    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;
    }
  2. 可选参数

    1
    2
    3
    function buildName(firstName: string, lastName?: string): void {

    }
  3. 支持默认参数 function Q(q = "xxx") {}

  4. 剩余参数

    1
    2
    3
    function buildName(firstName: string, ...restOfName: string[]) {

    }
  5. 支持重载

6. 泛型

  1. demo

    1
    2
    3
    4
    function identity<T>(arg: T): T {
    return arg;
    }
    const output = identity<string>("hello world");
  2. multi

    1
    2
    3
    function id<T, S>(argA: T, argB: S): S {

    }
  3. interface

    1
    2
    3
    interface A {
    <T>(arg: T): T;
    }

7. 枚举

  1. enum Direction { Up=1, Down, Left, Right }

  2. 默认从 0 开始

  3. 数字枚举 or 字符串枚举。

  4. 异构枚举(Heterogeneous enums)

  5. 反向映射

    1
    2
    3
    4
    5
    6
    enum Enum {
    A
    }

    let a = Enum.A;
    let nameofA = Enum[a]; // "A"
  6. const enum { A, B }

  7. 外部枚举

  8. 外部枚举是用来引入其他非 ts 代码的外部包已存在的枚举

    说白了就是写在 index.d.ts 里

    1
    2
    3
    4
    5
    declare enum Enum {
    A = 1,
    B,
    C = 2
    }

8. 类型推论

  1. (number|string|boolean)

9. 类型兼容性

  1. 兼容性只得是某个对象能否被赋值给另一个对象

  2. 属性相同的 class 和 interface 可以兼容。

  3. 函数比较的是参数

    1
    2
    3
    4
    5
    let x = (a: number) => 0;
    let y = (b: number, s: string) => 0;

    y = x; // OK
    x = y; // Error
  4. 可选参数和剩余参数在转换时不会产生错误

  5. 类之对比实例成员,不对比静态成员和构造函数

  6. private 和 protect 只允许赋值给父类。

  7. 泛型在结构相同的情况下可以转换,但是前提是不包含属性不同的成员。如果两者都是 any 就不会有问题。

10. 高级类型

10.1 交叉类型(Intersection Types)

  1. 指的是某个对象拥有多个 type,属于多个 type 的成员。

  2. T & U

  3. 检查类型检查的是所拥有的 key。如果吧 A、B 的key 都复制到 C 上面。那么typeof C == typeof A & typeof B

  4. demo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function 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)

  1. number | string
  2. 出参如果是联合类型,因为无法确定具体属于什么类型。导致只有两个类型共有部分的属性可以被使用。
  3. 可以使用强制转换。

10.3 类型保护与区分类型(Type Guards and Differentiating Types)

  1. 为了解决 联合类型 输出参数类型不确定问题

  2. 类型断言

    1
    2
    3
    4
    let pet = getSmallPet();
    if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
    }
  3. 用户自定义的类型保护

    1
    2
    3
    4
    5
    6
    7
    function isFish(pet: Fish|Bird): pet is Fish {
    return <Fish>pet).swim !== undefined;
    }

    if (isFish(pet)) {
    pet.swim();
    }
  4. typeof 类型保护

    1. typeof 仅支持 number, string, boolean, symbol
    2. if (typeof xx === 'string') { ... }
  5. instanceof 类型保护

    1. 此构造函数的 prototype属性的类型,如果它的类型不为 any的话
    2. 构造签名所返回的类型的联合

    上面两个感觉是机翻,没有例子也看不懂。

  6. 可以为 null 的类型

    1. null与 undefined是所有其它类型的一个有效值。
    2. --strictNullChecks标记可以解决此错误
  7. 类型别名

    1. type

    2. 不能出现在创建一个<声明>时,右侧有任何的声明

    3. <我感觉一辈子都不会这么去用 orz>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    type 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;
    1. type 和 interface 的区别

      1. type 更像是一个指针,在很多地方【错误】【提示】 等都会显示 type 原始的类型。
      2. interface 可以被 extends 和 implement 而 type 不行。
  8. 字符串字面量类型

    1. type Easing = "ease-in" | "ease-out" | "ease-in-out";
    2. 字符串字面量类型还能区别与 string 而用于重载。
  9. 数字子面型类型

    1. 同上
    2. 可以用来调试代码,缩小 number 范围带来的变量的报错。
  10. 枚举成员类型

    参照 <枚举> 。

  11. 可辨识联合(Discriminated Unions)

    1. <标签联合>、<代数数据联合>

    2. 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
      27
      interface 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
      }
      }
  12. 多态的 this 类型

    1. 菊花链的故事
  13. 索引类型(Index Types)

    1. let personProps: keyof Person; // 'name' | 'age'

    2. demo

      1
      2
      3
      function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
      return o[name]; // o[name] is of type T[K]
      }
  14. 映射类型

    1. 可以支持把一个类型的 type 转换成另个类型的 type。

    2. 常用的几个类型

      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;
      }
    3. 包装 & 拆包

      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);
    4. 实际包装完整代码(琢磨了半天)

      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
      type 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'});
    5. 类似 K extends keyof T。因为 keyof 类似 Object.keys 返回的是复数的属性,而且在<> 也可以传入类似 <'aa'|'bb'>。所以在使用必须用 [key in K]

    6. 预定义的有条件类型

      TypeScript 2.8在lib.d.ts里增加了一些预定义的有条件类型:

      • Exclude<T, U> – 从T中剔除可以赋值给U的类型。
      • Extract<T, U> – 提取T中可以赋值给U的类型。
      • NonNullable<T> – 从T中剔除nullundefined
      • ReturnType<T> – 获取函数返回值类型。
      • InstanceType<T> – 获取构造函数类型的实例类型。

11. Symbols

  1. let sym1 = Symbol(); let sym2 = Symbol("key");
  2. 还有很多内置的特性参照:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator

12. 迭代器 & 生成器

  1. for...of
  2. for...in

13. 模块

  1. export

  2. export xx as ‘yy’

  3. export default

  4. import

  5. import * as xx from ‘xx’;

  6. CommonJS 和 AMD 的 exports

    1. export =
    2. import xx require=
  7. 使用其他的 JavaScript 库

    1. 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
2
3
4
5
6
7
8
9
namespace Validation {
export interface xxx {

}

export class Q {

}
}
  1. 多个文件的同个命名空间会把他们合并到一起
  2. /// <reference path="Test.ts" /> 要预先引用
  3. 引入原生 js 库。需要提供 .d.ts 文件

15. 命名空间 & 模块

  1. 内部模块 = namespace + reference
  2. 外部模块 = export + import
  3. 拒绝 export namespace XX {}

16. 模块解析

  1. export xx;export yy = import { xx, yy } from './module'
  2. export xx;export yy; = import * as zz from './module'; zz.xx; zz.yy;
  3. export default xx; = import xx from './module';
  4. module.exports = xx = import xx = require('module');

17. 声明合并

  1. 接口 (interface) 可以合并

  2. 空间(namespace) 可以合并

  3. 非导出成员仅在其原有的(合并前的)命名空间内可见。

  4. 命名空间和类 - 命名空间里的成员必须导出,才能在类里面被调用

  5. 全局扩展

    1
    2
    3
    4
    5
    declare global {
    interface xxx {

    }
    }

18. JSX

  1. .tsx

19. 装饰器

  1. 配置

    1
    2
    3
    4
    5
    6
    {
    "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true
    }
    }
  2. 基本格式

    1
    2
    3
    4
    function f() {
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
    }
    }

20. Mixins

  1. demo

    1
    2
    3
    4
    5
    6
    7
    function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
    derivedCtor.prototype[name] = baseCtor.prototype[name];
    })
    });
    }
  2. 用与混合两个类

  3. class newClass implements oldClassA, oldClassB {}

  4. 需要在 newClass 简单的实现需要转移的参数和方法

21. 三斜线指令

  1. 三斜线指令是包含单个XML标签的单行注释
  2. ``/// `
  3. /// <reference path="..." />

22. JavaScript 文件类型检查

  1. 支持用 JSDoc 类型来表示类型信息(js 文件)

  2. 支持的 JSDoc

    • @type
    • @param (or @arg or @argument)
    • @returns (or @return)
    • @typedef
    • @callback
    • @template
    • @class (or @constructor)
    • @this
    • @extends (or @augments)
    • @enum

100. Demo

  1. Pick

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    interface a {
    a: string;
    };

    interface b {
    a: any
    }

    interface d extends Pick<a, keyof b> {
    c: number;
    }

    const test: d = {
    a: '123',
    c: 123
    };
  2. Omit

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    interface a {
    a: string;
    };

    interface b {
    a: any
    }

    interface d extends Omit<a, keyof b> {
    a: number;
    }

    const test: d = {
    a: 1
    };
CATALOG
  1. 1. 1. 基础类型
  2. 2. 2. 变量声明
  3. 3. 3. 接口
  4. 4. 4. 类
  5. 5. 5. 函数
  6. 6. 6. 泛型
  7. 7. 7. 枚举
  8. 8. 8. 类型推论
  9. 9. 9. 类型兼容性
  10. 10. 10. 高级类型
    1. 10.1. 10.1 交叉类型(Intersection Types)
    2. 10.2. 10.2 联合类型(Union Types)
    3. 10.3. 10.3 类型保护与区分类型(Type Guards and Differentiating Types)
  11. 11. 11. Symbols
  12. 12. 12. 迭代器 & 生成器
  13. 13. 13. 模块
  14. 14. 14. 命名空间
  15. 15. 15. 命名空间 & 模块
  16. 16. 16. 模块解析
  17. 17. 17. 声明合并
  18. 18. 18. JSX
  19. 19. 19. 装饰器
  20. 20. 20. Mixins
  21. 21. 21. 三斜线指令
  22. 22. 22. JavaScript 文件类型检查
  23. 23. 100. Demo