Skip to content

ts 记录

用 eslint 校验 ts 类型

https://typescript-eslint.io/getting-started

学习 ts 不错的资源

https://github.com/xcatliu/typescript-tutorial

两个对象类型如何进行收窄

如下有两个对象类型:

ts
interface Obj1 {
  a: string,
  b: string
}

interface Obj2 {
  c: string,
  e: string
}

使用关键字 in

ts
if ('a' in res) {
  res.a
  res.b
} else {
  res.c
  res.d
}

快速转为字面量类型

先了解下 ts 的自动类型推断:

ts
// ts 会自动类型推断,因为 changingString 后面又可能被改变,所以被认为是 string 类型
let changingString = "Hello World";
// changingString = "Olá Mundo";

// constantString 以 const 修饰,不会被改变,所以被认为是字面量类型 "Hello World"
const constantString = "Hello World";

然后,我们来看看这个例子:

ts
declare function handleRequest(url: string, method: "GET" | "POST"): void;
 
const req = { url: "https://example.com", method: "GET" };

handleRequest(req.url, req.method);

案例说 ts 应该编译通过,可是却报错了:

ts
declare function handleRequest(url: string, method: "GET" | "POST"): void;
 
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'. 

这是因为 req.method 被推断为 string 类型,而不是字面量类型 "GET"。

好在 ts 提供了一种快速将对象转为字面量类型的方法,添加后缀 as const

ts
// 编译通过 √
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);

强转类型

一个变量是 10,你却说它是 string 类型,ts 是不允许你这么做的。就像下面这样:

ts
const a = 10 as string;
// Conversion of type 'number' to type 'string' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.ts(2352)

如果你就是想把它变成其它类型,先断言为 anyunkonw 类型。这种办法又叫双重断言。

ts
// 编译通过

const a = '124t' as unknown as number
const b = '124t' as any as number
const c = '124t' as any as number

告诉 ts 更确切的类型

有时候你可能比 ts 知道了解得更多。

比如,你正在使用 document.getElementById,ts 仅仅知道这肯定会返回 HTMLElemnt 类型。但是你知道通过 id 你一定能拿到 HTMLCanvasElement。

在这种情况下,你就可以告诉 ts 这个变量的确切类型。用术语来讲叫做“类型断言”。

ts
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

也可以使用尖括号语法:

ts
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

类型别名和接口的最大不同

最大不同是它们的扩展方式。

  1. 在已有类型的基础上扩展新的类型。
ts
interface A {
  a: string
}

interface B extends A {
  b: number
}
ts
type A = {
  a: string
}

// 注意这里是 & 交集,不是 | 联合类型。如果写 | 联合类型,那么会导致类型收窄。
// 这里 & 可以理解为既有 A 的结构,又有 {b: number} 的结构。
type B = A & {
  b: number
}
  1. 往原有类型上添加新的类型
ts
interface A {
  a: string
}

interface A {
  b: number
}

function fn(obj: A) {
  console.log(obj.a) // ok
  console.log(obj.b) // ok
}
ts
// 做不到,会报错
// Error: Duplicate identifier 'Window'.
// type Window = {
//   title: string;
// }

// type Window = {
//   ts: TypeScriptAPI;
// }

使用联合类型可能需要类型收窄

比如下面的例子,会报错。

ts
function printId(id: number | string) {
  console.log(id.toUpperCase());
// Property 'toUpperCase' does not exist on type 'string | number'.
//   Property 'toUpperCase' does not exist on type 'number'.
}

进行类型收窄后,就不会报错了。

ts
function printId(id: number | string) {
  if (typeof id === "string") {
    // In this branch, id is of type 'string'
    console.log(id.toUpperCase());
  } else {
    // Here, id is of type 'number'
    console.log(id);
  }
}

但如果两个类型拥有相同的方法,就不需要类型收窄。

ts
function getFirstThree(x: number[] | string) {
  return x.slice(0, 3);
}```

## union 联合类型的含义
```ts
type AS = 'a' | 'asfd'

含义为被赋予的类型是这些类型中的其中一个。

是否开启严格模式

严格模式的其实是四个设置的总开关,用 strict 表示,默认是不开启的。

四个设置中包含 noImplicitAny、strictNullChecks。

是否允许有隐式的 any 类型

开启 noImplicitAny 选项后,代码中就不能有隐式的 any 类型。比如下面的代码的函数参数就是隐式的 any 类型:

ts
function fn(a, b) {
  console.log(a.radius)
}

strictNullChecks 的作用

ts 默认关闭了 strictNullChecks,但是推荐开启该设置。

关闭 strictNullChecks:那么当 value 可能是 null 或 undefined 时也能被获取,并且 null 或 undefined 也能赋值给其它类型变量。

ts
// 当 value 可能是 null 或 undefined 时也能被获取
function doSomething(x: string | null) {
  console.log("Hello, " + x.toUpperCase()); // ok
}

// null 或 undefined 也能赋值给其它类型变量
const a = null;
let c = 123;
c = a;

// 这样是可以的,相当于一开始就声明为 null
// let c = a;

如果设置 strictNullChecks 为 true,那么你使用可能为 null 或 undefined 的变量,需要进行类型收窄。

既然 ts 能转译 es6 代码,那 ts 能替代 babel 吗?

ts 可以设置 target 选项来指定转译后的代码是什么样的。默认是 es3,一个超级老的 js 版本。

虽然 tsconfig.js 可以配置 ts 代码转译为 es5,但是只能实现语法转换,如箭头函数转换等;但 api 类如 promise、set 等是无法转换的。所以仍需要 babel 来进行兼容性处理。

比如我有一个 ts 文件 temp.ts:

ts
const a = new Set([1,2,3])
console.log(a)
const b = '123'
console.log(`hello ${b}`)

直接使用 tsc ./temp.ts 命令转译该文件,默认 target 是 ES3,但只有语法会转换,其它 api 和类等是没法转换的。如下是转换后的文件 temp.js 代码:

js
var a = new Set([1, 2, 3]);
console.log(a);
var b = '123';
console.log("hello ".concat(b));

在检查到类型错误时不要转换代码

即使 ts 编译器在检查到类型错误,默认也会将 ts 文件转换为 js 文件。

如果我们想让 tsc(ts complier)在检查到类型错误时不要转换 ts 文件,可以设置 noEmitOnError 这个选项。

如果我们使用的是命令行,可以这样指定参数:

shell
tsc --noEmitOnError hello.ts

对象属性的子集匹配类型,检查就能通过

logPoint 函数的参数类型为 Point。

ts
interface Point {
  x: number;
  y: number;
}
 
function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

ts 检查通过。只要结构相同即可,ts 会自动推断类型。

ts
// logs "12, 26"
const point = { x: 12, y: 26 };
logPoint(point);

ts 检查通过。对象属性的子集能匹配类型,就能通过检查。

ts
const point3 = { x: 12, y: 26, z: 89 };
logPoint(point3); // logs "12, 26"

ts 检查通过。对象属性的子集匹配类型,通过检查。

ts
const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect); // logs "33, 3"

不通过。对象属性的子集不匹配类型,结构也不一样。

ts
const color = { hex: "#187ABF" };
logPoint(color);

const color2 = {x: 1}
logPoint(color2)

类和对象是一样的。下面的检查也能通过。

ts
class VirtualPoint {
  x: number;
  y: number;
 
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

const newVPoint = new VirtualPoint(13, 56);
logPoint(newVPoint); // logs "13, 56"

学习泛型

你可以使用泛型来声明你自己的类型:

ts
// 声明了一个类型 Backpack
interface Backpack<Type> {
  add: (obj: Type) => void;
  get: () => Type;
}

// 这一行是个简写,用来告诉 ts 这里有一个常量叫做 `backpack`,
// 让 ts 不要担心这个常量来自哪里。
declare const backpack: Backpack<string>; 

// object 的类型是 string,因为我们在上面已经声明:string 是 Backpack 类型的泛型变量
const object = backpack.get();
 
// 因为 backpack 的泛型变量是一个字符串 string 类型,所以你不能传递一个 number 类型给 add 函数
// backpack.add(23);
// error: Argument of type 'number' is not assignable to parameter of type 'string'.

Released under the MIT License.