1. void vs never
一个没有返回值的函数,返回类型是void。
一个抛出error的函数,或者循环执行永远不会到最后一行的函数,返回就是never。
参考 typescript never
2. interface vs type
参考自typescript example
多数情况下interface和type同样的方式使用,并且他们的类型只要结构一致,实例是可以互现赋值的:
type BirdType = {
wings: 2;
};
interface BirdInterface {
wings: 2;
}
const bird1: BirdType = { wings: 2 };
const bird2: BirdInterface = { wings: 2 };
const bird3: BirdInterface = bird1;
它们都支持扩展已定义的类型,一个是用intersection,一个是用extends:
// intersection
type Owl = { nocturnal: true } & BirdType;
type Robin = { nocturnal: false } & BirdInterface;
// extends
interface Peacock extends BirdType {
colourful: true;
flies: false;
}
interface Chicken extends BirdInterface {
colourful: false;
flies: false;
}
另一个差异在于,interface允许修改(通过同名定义),而type重复定义则会报错:
interface Kitten {
purrs: boolean;
}
interface Kitten {
colour: string;
}
type Puppy = { //error
color: string;
};
type Puppy = { //error
toys: number;
};
3. 什么时候使用type of
如果明确知道类型,就用类型;如果只知道值,那就可以通过type of来引用类型
interface U { id: number }
const u: U ={id:1};
const u2: (typeof U) ={id:1}; // 'U' only refers to a type, but is being used as a value here.
const u3: (typeof u) ={id:1};
console.log(u);
console.log(u2);
console.log(u3);
4. enum
为什么使用enum类型?
ts中的enum有什么值得注意的?
- 包含数字枚举、字符串枚举、混合枚举
- 不指定枚举值的值,会默认按照数字从0开始递增
- 枚举成员也可以被当作类型使用
- const定义的常量枚举不允许使用计算属性
5. 函数对象类型
有三种方式可以定义函数对象类型
let fn: (a: number, b: number) => number;
type Add = (a: number, b: number) => number;
interface Idd {
(a: number, b: number): number;
}
函数对象类型中混合属性的用法
interface Lib {
(): void
version: string;
doSomething: () => void;
}
let lib: Lib = ({} as Lib);
lib.version = '1.0.0';
lib.doSomething = () => {};
函数重载需要在最宽泛的定义中实现
function add(...args: number[]): number;
function add(...args: string[]): string;
function add(...args: any[]): any {
let first = args[0];
if (typeof first === 'number') {
return 1;
} else if (typeof first === 'string') {
return 'a';
}
}
console.log(add(1, 2));
console.log(add('a', 'b'));
6. interface和class的关系
参考 类与接口的关系
7. 使用namespace
JavaScript中的命名空间
在JavaScript没有引入模块系统之前,声明变量都是全局的容易彼此污染,因此通过把变量放在 对象直接量 下实现了命名空间。
TS中有命名空间的发展过程
早期版本中用
Internal
早期叫做内部模块,本质上是个闭包,用于隔离作用域;
随着ES6引入,不再叫内部模块,而保留了命名空间,用以兼容全局变量。
TS中的命名空间怎么书写
src/a.ts
namespace Shape {
const pi = Math.PI
export function circle(r: number) {
return pi * r ** 2
}
}
- namespace本身不用加export
- 希望通过
Shape.
使用的属性需要加export - 编译上述文件
tsc src/a.ts
得到的结果如下 ```json var Shape; (function (Shape) { var pi = Math.PI; function circle(r) {
} Shape.circle = circle; })(Shape || (Shape = {}));return pi * Math.pow(r, 2);
<a name="BWdNv"></a>
#### 命名空间的联合
src/b.ts
```json
namespace Shape {
export function square(x: number) {
return x * x
}
}
console.log(Shape.circle(1))
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
/// <reference path="a.ts" />
namespace Shape {
export function square(x: number) {
return x * x
}
}
import circle = Shape.circle
console.log(circle(1))
console.log(Shape.square(2))
另外,一个简化命名空间变量的方式是,通过import语法(注:和ES6中的import不是一回事)。
使用命名空间要注意的
使用命名空间下实际上是创建了全局闭包,因此建议不要和模块一起使用,而是当作全局模块,tsc 生成 .js
文件后,通过在html中引用的方式使用。
疑问点
尽管上述 tsc 某个文件,发现使用了不存在的命名空间 或 命名空间下的变量会报错,但是当我们直接在启动好的项目中,在模块中使用命名空间,是不会有类似报错的,是那部分配置替我们做了这样的事呢?
8. 声明合并
interface X {
x: number
foo(bar: number): number
}
interface X {
y: number
foo(bar: string): string
foo(bar: string[]): string[]
foo(bar: 'a'): string
}
let x: X = {
x: 1,
y: 2,
foo(bar: any) {
return bar
},
}
console.log(x.foo('a'))
function Lib() {}
namespace Lib {
// 增加了一个属性
export let version = '1.0'
}
console.log(Lib.version)
class C {}
namespace C {
export let state = 'state1'
}
console.log(C.state)
enum Color {
RED,
GREEN,
YELLOW,
}
namespace Color {
export function mix() {}
}
console.log(Color)
- 接口的声明合并(变量、函数)
- 函数和namespace的声明合并
- 类和namespace的声明合并
- 枚举和namespace的声明合并
函数参数少的可以复制给参数多的:在函数重载中,函数被实现在更宽泛的定义中。如何理解这个词。
namespace声明合并中,namespace要出现在函数、类的后面(枚举为什么不需要,当作思考)。
不建议使用声明合并,但是为了支持老的开发习惯仍旧保留,并且namespace如果出现在前面会导致报错(即类库还没引入进来,就开始在下面扩展),能帮助我们发现代码中的问题。