typescript 基础总结

2021-11-24  本文已影响0人  前端小白的摸爬滚打

先提一嘴:TypeScript 是结构类型系统,类型之间的对比只会比较它们最终的结构,而会忽略它们定义时的关系。

类型

包含 js 的所有数据类型

declare enum Directions {
  Up,
  Down,
  Left,
  Right
}
let directions = [
  Directions.Up,
  Directions.Down,
  Directions.Left,
  Directions.Right
];
console.log(directions);

编译结果

var directions = [
  Directions.Up,
  Directions.Down,
  Directions.Left,
  Directions.Right
];
console.log(directions);

⚠️ 运行报错

补充说明

object、Object、{} 类型:

Object 类型

代表 Object 实例的类型,也就是说该类型的变量可以访问 Object 接口上定义的类型,也就是 Object.prototype 上的类型,而且接受原始类型的值
Object 类的所有实例都继承了 Object 接口中的所有属性。

为什么?Object.prototype 的属性也可以通过原始值访问

// node_modules/typescript/lib/lib.es5.d.ts

interface Object {
  constructor: Function;
  toString(): string;
  toLocaleString(): string;
  valueOf(): Object;
  hasOwnProperty(v: PropertyKey): boolean;
  isPrototypeOf(v: Object): boolean;
  propertyIsEnumerable(v: PropertyKey): boolean;
}
// node_modules/typescript/lib/lib.es5.d.ts

interface ObjectConstructor {
  /** Invocation via `new` */
  new (value?: any): Object;
  /** Invocation via function calls */
  (value?: any): any;

  readonly prototype: Object;

  getPrototypeOf(o: any): any;

  // ···
}

declare var Object: ObjectConstructor;
object

object 表示非原始类型,只能访问该类型的变量的 Object.prototype 上的属性和方法(通过继承访问的)

可以对 object 类型赋值任意的非原始数据类型,不会进行类型检查,相反 Object 复制的非原始数据类型,如果值对象属性名与 Object 接口中的属性冲突,则 TypeScript 编译器会提示相应的错误

// Type '() => number' is not assignable to type
// '() => string'.
// Type 'number' is not assignable to type 'string'.
const obj1: Object = {
  toString() {
    return 123;
  } // Error
};
const obj2: object = {
  toString() {
    return 123;
  }
};
{} 类型

和 object 类型类似,但是它可以被赋值为原始数据类型。只能访问该类型变量 Object.prototype 上的属性和方法

总结

接口(interface)

用来描述对象的形状

可以用来为对象、数组、函数定义类型

但是常用来定义对象的类型

当为具体接口的变量赋一个字面量的值的时候,属性不能多也不能少,但是如果赋的值是一个变量,那么只要该变量包含接口定义的必选属性即可

特点:

类型别名(type)

给类型起一个别名,和 interface 功能很类似,简述区别

type 和 interface 的异同

相同点

区别

类型别名和接口应该用哪一个?

建议在可以使用接口的情况下,尽可能使用接口

原因:

泛型

泛型就是带有类型参数的类型

这个类型参数我们必须在使用该类型的时候指定或者是在我们的值中使用 TS 才可以为其推断出正确的类型

泛型约束 extends

泛型的默认值

infer

表示在 extends 条件语句中待推断的类型变量。

type ParamType<T> = T extends (...args: infer P) => any ? P : T;

整句表示为:如果 T 能赋值给 (...args: infer P) => any,则结果是 (...args: infer P) => any 类型中的参数 P,否则返回为 T。

类型断言

as / <type>

可以进行类型断言的条件:

interface A {
  a: string;
  b: string;
}

interface B {
  b: string;
}

const aa: A = { a: "1", b: "2" };

const bb: B = aa as B; // ok
interface A {
  a: string;
  b: string;
}

interface B {
  b: string;
  c: string;
}

const aa: A = { a: "1", b: "2" };

const bb: B = aa as B; // no ok A 类型和 B 类型不兼容

类型断言需要谨慎使用,因为可能会导致一些运行时的错误

双重断言

anytype as unknown/any as othertype

上面这个万能语句可以帮助我们实现任意类型之间的转换,而且不用管是否兼容,但是很危险 ⚠️,谨慎使用 😂

类型断言 vs 类型转换

类型断言并不会改变变量的类型,只是让跳过 TS 的类型检查

声明合并

声明文件

FAQ

前者已经被废弃,使用 declare namespace A 代替;后者用于扩展一个已有的模块 a。全局文件下的 declare module 'xx' 会在全局环境生成一个名为 xx 的模块,并且可以在里面定义这个 xx 模块应该有的导出,一般用来添加或补充 node_modules 中的模块的声明文件。同名的 declare module 里面的导出会合并。

声明空间

变量声明空间

声明的都是变量,不能作为类型使用

类型声明空间

声明的都是类型,不能作为值来使用,例如 interface

命名空间(namespace)

为了防止变量污染,在命名空间内部定义的变量/函数/类,只有 export 出来的才可以被外界所访问。
js 在有模块化之前,我们也会使用全局对象+立即执行函数来避免全局变量的污染

namespace Utility {
  export function log(msg) {
    console.log(msg);
  }
  export function error(msg) {
    console.log(msg);
  }
}

// usage
Utility.log("Call me");
Utility.error("maybe");

编译之后

"use strict";
var Utility;
(function(Utility) {
  function log(msg) {
    console.log(msg);
  }
  Utility.log = log;
  function error(msg) {
    console.log(msg);
  }
  Utility.error = error;
})(Utility || (Utility = {}));
// usage
Utility.log("Call me");
Utility.error("maybe");
var something;
(function(something) {
  something.foo = 123;
})(something || (something = {}));

console.log(something);
// { foo: 123 }

(function(something) {
  something.bar = 456;
})(something || (something = {}));

console.log(something); // { foo: 123, bar: 456 }

快速为第三方库或者是模块定义类型

declare module "*.css";

现在你可以使用 import * as foo from './some/file.css'

@types

npm install -D @types/jquery

可以通过这个方式来为项目添加 jquery 的类型声明文件,然后我们可以在全局文件中(没有 import/export)使用 $

在模块(有 export/import 的文件)中我们可以通过下面的方式来使用

import * as $ from 'jquery'`

环境声明

环境声明允许你安全的使用现在已有的 JS 库,用于为已经存在的变量、函数等添加类型定义。使用declare来添加类型定义

lib.d.ts

在我们安装 typescript 库的时候,自动为我们安装了一些声明文件,这些文件为添加了一些 JS/浏览器中全局变量/类型/构造函数的定义
此文件包含了 JavaScript 运行时以及 DOM 中存在各种常见的环境声明。可以让我们书写经过类型检查的 JS 代码。
我们可以在 tsconfig.json 中配置 lib 选项来配置

"compilerOptions": {
    "lib": ["dom", "es6"]
}

类型保护

常用来缩小联合类型的范围与 if...else 搭配使用

// 用户自己定义的类型保护!
function isFoo(arg: Foo | Bar): arg is Foo {
  return (arg as Foo).foo !== undefined;
}

辨析联合类型

刚好可以使用这个特性来实现上面的类型保护功能,指的是联合类型中的每个类型都有一个字面量类型的属性,我们可以通过这个属性来区分具体类型

typeof

keyof

属性访问操作符

[]

interface T {
  name: string;
}
type Name = T["name"]; // string

映射类型

通过in操作符实现

type Index = "a" | "b" | "c";
type FromIndex = { [k in Index]?: number };

高级类型

TS 内部实现了一些操作符

Partial

将属性变成可选

type Partial<T> = {
  [K in keyof T]?: T[K];
};

Readonly

将属性变为只读

type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

Pick

从一个类型中选出几个 k 组成一个新类型

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

类型的兼容性

变体

函数

参数个数

个数少的可以赋值给个数多的

返回值

协变

参数类型

逆变(开启"strictFunctionTypes"),否则是双变的

可选参数和 rest 参数

都是兼容的

let foo = (x: number, y: number) => {};
let bar = (x?: number, y?: number) => {};
let bas = (...args: number[]) => {};

foo = bar = bas;
bas = bar = foo;

可选的(上例子中的 bar)与不可选的(上例子中的 foo)仅在选项为 strictNullChecks 为 false 时兼容。

泛型

仅当类型参数在被一个成员使用时,才会影响兼容性

对象

要赋值的变量必须包含被赋值变量的所有属性,可以多,但是不可以少;而且这个检测是深层次的。

如果赋值的是一个字面量,那么这个字面量必须严格按照接口定义的形状实现

协变和逆变

函数的协变和逆变

函数的返回值是协变的,参数是逆变的,在开启 strictFunctionTypes 选项的时候

解释

假如我们有三种类型

Greyhound ≼ Dog ≼ Animal

可以得到:

Animal → Greyhound 是 Dog → Dog 的子类型

返回值是协变的:返回值 Greyhound 类型可以保证我们操作其任何 Dog 类型的属性/方法都是没有问题的,因为我们实际返回的类型是 Greyhound

参数是逆变的:可以保证我们在函数内部操作的任何 Animal 类型上的属性/方法都是可以的,因为我们在实际使用的时候传入的参数是 Dog 类型。Dog → Dog 要求传递的参数必须是 Dog 或者是 Dog 的子类,但是他们都是 Animal 的子类,所以在赋值的函数的内部其实使用的参数都是 Animal 类型且传递的参数必须是 Dog 或者是 Dog 的子类,所以他们一定包含 Animal 类型需要的值。

上一篇 下一篇

猜你喜欢

热点阅读