1. void vs never

一个没有返回值的函数,返回类型是void。
一个抛出error的函数,或者循环执行永远不会到最后一行的函数,返回就是never。
参考 typescript never

2. interface vs type

参考自typescript example
多数情况下interface和type同样的方式使用,并且他们的类型只要结构一致,实例是可以互现赋值的:

  1. type BirdType = {
  2. wings: 2;
  3. };
  4. interface BirdInterface {
  5. wings: 2;
  6. }
  7. const bird1: BirdType = { wings: 2 };
  8. const bird2: BirdInterface = { wings: 2 };
  9. const bird3: BirdInterface = bird1;

它们都支持扩展已定义的类型,一个是用intersection,一个是用extends:

  1. // intersection
  2. type Owl = { nocturnal: true } & BirdType;
  3. type Robin = { nocturnal: false } & BirdInterface;
  4. // extends
  5. interface Peacock extends BirdType {
  6. colourful: true;
  7. flies: false;
  8. }
  9. interface Chicken extends BirdInterface {
  10. colourful: false;
  11. flies: false;
  12. }

另一个差异在于,interface允许修改(通过同名定义),而type重复定义则会报错:

  1. interface Kitten {
  2. purrs: boolean;
  3. }
  4. interface Kitten {
  5. colour: string;
  6. }
  7. type Puppy = { //error
  8. color: string;
  9. };
  10. type Puppy = { //error
  11. toys: number;
  12. };

3. 什么时候使用type of

如果明确知道类型,就用类型;如果只知道值,那就可以通过type of来引用类型

  1. interface U { id: number }
  2. const u: U ={id:1};
  3. const u2: (typeof U) ={id:1}; // 'U' only refers to a type, but is being used as a value here.
  4. const u3: (typeof u) ={id:1};
  5. console.log(u);
  6. console.log(u2);
  7. console.log(u3);

4. enum

参考(中文翻译的一般)

为什么使用enum类型?

enum设计的用途就是 使用带名字的名字来描述用途。

ts中的enum有什么值得注意的?

  1. 包含数字枚举、字符串枚举、混合枚举
  2. 不指定枚举值的值,会默认按照数字从0开始递增
  3. 枚举成员也可以被当作类型使用
  4. const定义的常量枚举不允许使用计算属性

5. 函数对象类型

有三种方式可以定义函数对象类型

  1. let fn: (a: number, b: number) => number;
  2. type Add = (a: number, b: number) => number;
  3. interface Idd {
  4. (a: number, b: number): number;
  5. }

函数对象类型中混合属性的用法

  1. interface Lib {
  2. (): void
  3. version: string;
  4. doSomething: () => void;
  5. }
  6. let lib: Lib = ({} as Lib);
  7. lib.version = '1.0.0';
  8. lib.doSomething = () => {};

函数重载需要在最宽泛的定义中实现

  1. function add(...args: number[]): number;
  2. function add(...args: string[]): string;
  3. function add(...args: any[]): any {
  4. let first = args[0];
  5. if (typeof first === 'number') {
  6. return 1;
  7. } else if (typeof first === 'string') {
  8. return 'a';
  9. }
  10. }
  11. console.log(add(1, 2));
  12. console.log(add('a', 'b'));


6. interface和class的关系

参考 类与接口的关系
image.png

7. 使用namespace

JavaScript中的命名空间

在JavaScript没有引入模块系统之前,声明变量都是全局的容易彼此污染,因此通过把变量放在 对象直接量 下实现了命名空间。

TS中有命名空间的发展过程

早期版本中用
Internal

早期叫做内部模块,本质上是个闭包,用于隔离作用域;
随着ES6引入,不再叫内部模块,而保留了命名空间,用以兼容全局变量。

TS中的命名空间怎么书写

src/a.ts

  1. namespace Shape {
  2. const pi = Math.PI
  3. export function circle(r: number) {
  4. return pi * r ** 2
  5. }
  6. }
  • namespace本身不用加export
  • 希望通过Shape.使用的属性需要加export
  • 编译上述文件 tsc src/a.ts得到的结果如下 ```json var Shape; (function (Shape) { var pi = Math.PI; function circle(r) {
    1. return pi * Math.pow(r, 2);
    } Shape.circle = circle; })(Shape || (Shape = {}));
  1. <a name="BWdNv"></a>
  2. #### 命名空间的联合
  3. src/b.ts
  4. ```json
  5. namespace Shape {
  6. export function square(x: number) {
  7. return x * x
  8. }
  9. }
  10. console.log(Shape.circle(1))
  11. console.log(Shape.square(2))

另外一个文件中也声明了Shape,其中的export内容会被整合到一起。但是,如果你希望通过tsc src/b.ts的方式使用命名空间Shape.square,必须通过 reference 引入 a.ts 的定义,否则会报错:error TS2304: Cannot find name ‘Shape’.

src/b.ts

  1. /// <reference path="a.ts" />
  2. namespace Shape {
  3. export function square(x: number) {
  4. return x * x
  5. }
  6. }
  7. import circle = Shape.circle
  8. console.log(circle(1))
  9. console.log(Shape.square(2))

另外,一个简化命名空间变量的方式是,通过import语法(注:和ES6中的import不是一回事)。

使用命名空间要注意的

使用命名空间下实际上是创建了全局闭包,因此建议不要和模块一起使用,而是当作全局模块,tsc 生成 .js文件后,通过在html中引用的方式使用。

疑问点

尽管上述 tsc 某个文件,发现使用了不存在的命名空间 或 命名空间下的变量会报错,但是当我们直接在启动好的项目中,在模块中使用命名空间,是不会有类似报错的,是那部分配置替我们做了这样的事呢?

8. 声明合并

  1. interface X {
  2. x: number
  3. foo(bar: number): number
  4. }
  5. interface X {
  6. y: number
  7. foo(bar: string): string
  8. foo(bar: string[]): string[]
  9. foo(bar: 'a'): string
  10. }
  11. let x: X = {
  12. x: 1,
  13. y: 2,
  14. foo(bar: any) {
  15. return bar
  16. },
  17. }
  18. console.log(x.foo('a'))
  19. function Lib() {}
  20. namespace Lib {
  21. // 增加了一个属性
  22. export let version = '1.0'
  23. }
  24. console.log(Lib.version)
  25. class C {}
  26. namespace C {
  27. export let state = 'state1'
  28. }
  29. console.log(C.state)
  30. enum Color {
  31. RED,
  32. GREEN,
  33. YELLOW,
  34. }
  35. namespace Color {
  36. export function mix() {}
  37. }
  38. console.log(Color)
  1. 接口的声明合并(变量、函数)
  2. 函数和namespace的声明合并
  3. 类和namespace的声明合并
  4. 枚举和namespace的声明合并

函数参数少的可以复制给参数多的:在函数重载中,函数被实现在更宽泛的定义中。如何理解这个词。

namespace声明合并中,namespace要出现在函数、类的后面(枚举为什么不需要,当作思考)。

不建议使用声明合并,但是为了支持老的开发习惯仍旧保留,并且namespace如果出现在前面会导致报错(即类库还没引入进来,就开始在下面扩展),能帮助我们发现代码中的问题。