JavaScript的类型和检查

JavaScript的类型包含了:

  • 基础数据类型 boolean string number undefined null 等
  • 对象数据类型 object array function date 等
  • 在ES6中扩展 regexp、class等

在TypeScript中判定基础、数组类型:

  1. let str: string = 'hello';
  2. // 声明变量str
  3. // 变量类型限定为string(string作为语言内置类型不用再声明)
  4. // 定义了一个常量,常量的类型为字符串
  5. // ts检测:常量的类型,和变量str的类型是一样的,检查通过!
  6. let arr:number[] = [1,2,3];
  7. // ts检测:和字符串类似,略

在TypeScript中判定对象类型:

  1. interface User {
  2. name: string;
  3. }
  4. let user:User = {name: 'yefei'};
  5. // 通过interface语法定义User,变量类型限定为User
  6. // 声明变量user
  7. // 定义了一个常量,常量的类型为对象直接量
  8. // ts检测:对象直接量的结构,和变量user的类型是一样的,检查通过!

在TypeScript中判定函数类型(参考 www.typescriptlang.org):

  1. // 方式1
  2. let fn:(a:number, b:number) => number;
  3. fn = (c: number, d: number) => { return c + d };
  4. // 方式2
  5. type AddType = (a:number, b:number) => number;
  6. let fn:AddType;
  7. fn = (c: number, d: number) => { return c + d };
  8. // 方式3
  9. type UserCallable = {
  10. (name: string): void;
  11. }
  12. let u:UserCallable = function(name) { console.log(`hello ${name}`);}
  13. u('yefei');
  14. // 使用方式有很多,上述只列举了三种
  15. // ts检测:都是函数,切参数和返回值一致,检查通过

全局变量怎么增加声明

类型声明 值 / 值的类型
例子1 string ‘hello’ / string
例子2 number[] [1,2,3] / number[]
例子3 {
name: string;
}
{
name: ‘yefei’
}
例子4 (a:number, b:number) => number; (c: number, d: number) => { return c + d };

上述例子中,类型要么是语言中与定义的,要么是同一个文件中定义的。当.ts文件中使用变量值,而类型不在同一个文件中时,就需要给他们增加声明,否则会引起警告的。

给Window下增加方法

比如我们想在window对象下增加一个halo方法

  1. window.halo = (name: string) => console.log('hello ' + name);
  2. window.halo('yefei'); // Property 'halo' does not exist on type 'Window & typeof globalThis'.ts(2339)

通过在同名的 .d.ts文件中增加声明,在.tsx文件中增加实现来实现

  1. // index.d.ts
  2. interface Window {
  3. halo: (name: string) => void
  4. }
  5. // index.tsx
  6. window.halo = (name: string) => { console.log('halo ' + name);}
  7. window.halo('yefei');

注:关于声明文件,虽然使用 index.d.ts 后,在VSCode中不会出现提示语法错误了,但在编译器的命令行仍旧会提示找不到的错误,这是因为:

Please note that the compiler does not include files that can be possible outputs; e.g. if the input includes index.ts, then index.d.ts and index.js are excluded. In general, having files that differ only in extension next to each other is not recommended.

原因是“声明文件不能有同名的ts文件,因为会当作是从ts到d.ts自动生成的文件而忽略”。
将名字改为 global.d.ts即可,假定我们要实现一个完整的 EventEmitter的逻辑,会是怎样呢

给Window下增加类的对象

  1. // event-emitter.ts
  2. class EventEmitter {
  3. m = {}
  4. listen(name, handler) {
  5. this.m[name] = this.m[name] || []
  6. this.m[name].push(handler)
  7. }
  8. emit(name, data) {
  9. if (this.m[name]) {
  10. this.m[name].forEach((h) => {
  11. h(data)
  12. })
  13. }
  14. }
  15. }
  16. export default EventEmitter
  17. // global.d.ts
  18. interface EventEmitter {
  19. listen(name: string, callback: Function)
  20. emit(name: string, param: any)
  21. }
  22. interface Window {
  23. mb: typeof EventEmitter
  24. }

这里我们可以看到,global.d.ts 给mb声明的是一个类对象的结构,在实际代码中怕你没有往window.mb赋一个 new EventEmitter();对象,TS也无法检测出来,它只知道Window下与定义了什么方法,你调用为定义的、或类型不符合的时候,会给到你提示。

stackoverflow

  1. 如果所在文件没有import/export,它就是一个全局模块
  2. 否则, 需要使用declare global语法

全局模块和局部模块不可混用

在上例中,EventEmitter因为没有引用其他模块,会被当作全局声明;如果如果我们的 EventEmitter 是来自npm的类库,那么就是局部声明,需要通过global来提升为全局声明。那么写法会变成下面这样:

  1. // global.d.ts
  2. import EventEmitter from 'eventemitter3'
  3. declare global {
  4. interface Window {
  5. mb: EventEmitter
  6. }
  7. }

补充说明:最低成本地声明方法、类实例的方式,当然是 mb: any,应急使用可以,但是不可取。

文件因为引用外部类库已经变为了局部声明,需要通过global如果我们也在同样文件中声明了其他的全局变量,比如声明了一个全局模块,那么这个模块将不会有效,除非要么知道用什么语法声明gobal module,要么要将它移动到另外一个 .d.ts文件中。

  1. module 'manba' {
  2. function _Manba(): any
  3. function manba(p?: number): _Manba
  4. namespace _Manba {
  5. function distance(): number
  6. function format(reg: string): string
  7. }
  8. namespace manba {
  9. export const DAY: string
  10. export const YEAR: string
  11. }
  12. export default manba
  13. }

对象类型接口检查不通过

直接量的检查

下面的代码执行没有错误

  1. interface Result {
  2. id: number;
  3. name: string;
  4. }
  5. function render(r: Result) {
  6. console.log('result', r);
  7. }
  8. const res = {id: 1, name: 'yefei', age: 18};
  9. render(res);

稍微做点改动,将对象直接量而非变量直接传递给函数,就会报错

  1. render({id: 1, name: 'yefei', sex: 'male'}); // Argument of type '{ id: number; name: string; age: number; }' is not assignable to parameter of type 'Result'.

修正上述问题的两种办法(注意在JSX中, 第二种方式不可用,会被误识别)

  1. render({id: 1, name: 'yefei', sex: 'male'} as Result)
  2. render(<Result>{id: 1, name: 'yefei', age: 18}); // JSX中会被误识别

可选属性

如果需要检查对象中是否有某个字段,为了避免报错,可以使用可选属性

  1. interface Result {
  2. id: number;
  3. name: string;
  4. }
  5. function render(r: Result) {
  6. if (r.age) {
  7. console.log('age', r.age);
  8. } else {
  9. console.log('no age');
  10. }
  11. }
  12. render({id: 1, name: 'yefei', age: 18});

属性的索引签名

签名用于描述一定规则的属性和值,它分为字符串索引签名、数字索引签名。下面是例子:

  1. interface StringArray {
  2. [x: string]: any;
  3. }
  4. interface NumberArray {
  5. [x: number]: any;
  6. }
  7. let xs: StringArray = { a: '1', b: 2 };
  8. console.log(xs);
  9. let ns = [1, 2, 3];
  10. console.log(ns);

有两点值得注意:

  1. 索引签名会导致已经声明的属性冲突
  2. 字符串和数字索引签名,因为字符串会被转化成字符串会导致冲突,除非数字索引签名的返回范围更广 ```json interface Result { id: number; // Property ‘id’ of type ‘number’ is not assignable to string index type ‘string’. name: string;

}

interface Result {

[y: number]: number; // Numeric index type ‘number’ is not assignable to string index type ‘string’. } ```