ts 记录
用 eslint 校验 ts 类型
https://typescript-eslint.io/getting-started
学习 ts 不错的资源
https://github.com/xcatliu/typescript-tutorial
两个对象类型如何进行收窄
如下有两个对象类型:
interface Obj1 {
a: string,
b: string
}
interface Obj2 {
c: string,
e: string
}使用关键字 in。
if ('a' in res) {
res.a
res.b
} else {
res.c
res.d
}快速转为字面量类型
先了解下 ts 的自动类型推断:
// ts 会自动类型推断,因为 changingString 后面又可能被改变,所以被认为是 string 类型
let changingString = "Hello World";
// changingString = "Olá Mundo";
// constantString 以 const 修饰,不会被改变,所以被认为是字面量类型 "Hello World"
const constantString = "Hello World";然后,我们来看看这个例子:
declare function handleRequest(url: string, method: "GET" | "POST"): void;
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);案例说 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:
// 编译通过 √
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);强转类型
一个变量是 10,你却说它是 string 类型,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)如果你就是想把它变成其它类型,先断言为 any 或 unkonw 类型。这种办法又叫双重断言。
// 编译通过
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 这个变量的确切类型。用术语来讲叫做“类型断言”。
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;也可以使用尖括号语法:
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");类型别名和接口的最大不同
最大不同是它们的扩展方式。
- 在已有类型的基础上扩展新的类型。
interface A {
a: string
}
interface B extends A {
b: number
}type A = {
a: string
}
// 注意这里是 & 交集,不是 | 联合类型。如果写 | 联合类型,那么会导致类型收窄。
// 这里 & 可以理解为既有 A 的结构,又有 {b: number} 的结构。
type B = A & {
b: number
}- 往原有类型上添加新的类型
interface A {
a: string
}
interface A {
b: number
}
function fn(obj: A) {
console.log(obj.a) // ok
console.log(obj.b) // ok
}// 做不到,会报错
// Error: Duplicate identifier 'Window'.
// type Window = {
// title: string;
// }
// type Window = {
// ts: TypeScriptAPI;
// }使用联合类型可能需要类型收窄
比如下面的例子,会报错。
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'.
}进行类型收窄后,就不会报错了。
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);
}
}但如果两个类型拥有相同的方法,就不需要类型收窄。
function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}```
## union 联合类型的含义
```ts
type AS = 'a' | 'asfd'含义为被赋予的类型是这些类型中的其中一个。
是否开启严格模式
严格模式的其实是四个设置的总开关,用 strict 表示,默认是不开启的。
四个设置中包含 noImplicitAny、strictNullChecks。
是否允许有隐式的 any 类型
开启 noImplicitAny 选项后,代码中就不能有隐式的 any 类型。比如下面的代码的函数参数就是隐式的 any 类型:
function fn(a, b) {
console.log(a.radius)
}strictNullChecks 的作用
ts 默认关闭了 strictNullChecks,但是推荐开启该设置。
关闭 strictNullChecks:那么当 value 可能是 null 或 undefined 时也能被获取,并且 null 或 undefined 也能赋值给其它类型变量。
// 当 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:
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 代码:
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 这个选项。
如果我们使用的是命令行,可以这样指定参数:
tsc --noEmitOnError hello.ts对象属性的子集匹配类型,检查就能通过
logPoint 函数的参数类型为 Point。
interface Point {
x: number;
y: number;
}
function logPoint(p: Point) {
console.log(`${p.x}, ${p.y}`);
}ts 检查通过。只要结构相同即可,ts 会自动推断类型。
// logs "12, 26"
const point = { x: 12, y: 26 };
logPoint(point);ts 检查通过。对象属性的子集能匹配类型,就能通过检查。
const point3 = { x: 12, y: 26, z: 89 };
logPoint(point3); // logs "12, 26"ts 检查通过。对象属性的子集匹配类型,通过检查。
const rect = { x: 33, y: 3, width: 30, height: 80 };
logPoint(rect); // logs "33, 3"不通过。对象属性的子集不匹配类型,结构也不一样。
const color = { hex: "#187ABF" };
logPoint(color);
const color2 = {x: 1}
logPoint(color2)类和对象是一样的。下面的检查也能通过。
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"学习泛型
你可以使用泛型来声明你自己的类型:
// 声明了一个类型 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'.