Ts入门到放弃
TS 的核心能力在于给 JS 提供静态类型检查,是有类型定义的 JS 的超集,包括 ES5、ES5+ 和其他一些诸如泛型、类型定义、命名空间等特征的集合。
本次仅会针对类型声明部分配合示例进行着重介绍,更详细的内容以及特性可以查看 Typescript handbook以及changelog。
TOC
- 类型 & 操作符
- 没有明确的指定类型的时候推测出一个类型
- 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
- 声明 & 模块
- 泛型
- 体操
- 拓展
类型
- Function、object、Object、Array、number、string、enum、any、unknown、undefined、void、null、never
- tuple []
- intersection &
- union |
- 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
- 联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型
结构子类型
typescript 的子类型是基于 结构子类型 的,只要结构可以兼容,就是子类型(Duck Type)
class Test {x: number;
}function get(test: Test) {return test.x;
}class Test2 {x: number;
}const test2 = new Test2();// Passed
get(test2);
java、c++ 等传统静态类型语言是基于 名义子类型 的,必须显示声明子类型关系(继承),才可以兼容
对象子类型
子类型中必须包含源类型所有的属性和方法
function get(test: { x: number }) {return test.x;
}const test = {x: 1,y: "2",
};// Passed
get(test);
注意: 如果直接传入一个对象字面量是会报错
function get(test: { x: number }) {return test.x;
}// Error!
// Argument of type '{ x: number; y: string; }' is not assignable to parameter of type '{ x: number; }'.
// Object literal may only specify known properties, and 'y' does not exist in type '{ x: number; }'.
get({ x: 1, y: "2" });
这是 ts 中的另一个特性,叫做: excess property check,当传入的参数是一个对象字面量时,会进行额外属性检查
函数子类型(逆变与协变)
对于函数类型来说,函数参数的类型兼容是反向的,我们称之为 逆变 返回值的类型兼容是正向的,称之为 协变
我们允许一个函数类型中,返回值类型是协变的,而参数类型是逆变的。返回值类型是协变的,意思是 A ≼ B 就意味着 (T → A) ≼ (T → B) 。参数类型是逆变的,意思是 A ≼ B 就意味着 (B → T) ≼ (A → T) ( A 和 B 的位置颠倒过来了)
允许不变的列表(immutable)在它的参数类型上是协变的,但是对于可变的列表(mutable),其参数类型则必须是不变的(invariant),既不是协变也不是逆变
逆变与协变并不是 TS 中独有的概念,在其他静态语言(java 中数组即是逆变又是协变)中也有相关理念
若
- A ≼ B 表示 A 是 B 的子类型,A 包含 B 的所有属性和方法。
- A => B 表示以 A 为参数,B 为返回值的方法。 (param: A) => B
如果我们现在有三个类型 Animal、 Human、 Man,那么肯定存在下面的关系:
Man ≼ Human ≼ Animal
所以存在 Animal => Man 是 Human => Human 的子类型,可以简单理解为对于参数的类型由于逆变可以接收参数类型的父类型,对于函数的返回值由于协变可以接收返回值类型的子类型
函数的参数为多个时可以转化为 Tuple 的类型兼容性,长度大的是长度小的子类型,再由于函数参数的逆变特性,所以函数参数少的可以赋值给参数多的(参数从前往后需一一对应)
具体 demo 和示例可以参考链接
What are covariance and contravariance?
在 TypeScript 中, 参数类型是双向协变的 ,也就是说既是协变又是逆变的,而这并不安全。但是现在你可以在 TypeScript 2.6 版本中通过 --strictFunctionTypes 或 --strict 标记来修复这个问题。
逆变协变本质上还是为了满足里式替换
any、unknown、void、never
- any: 任意类型(属于 Union type)
- unknown: 未知的类型
任何类型都能分配给 unknown,但 unknown 不能分配给其他基本类型,而 any 可以分配和被分配任意类型
- void: TypeScript中的void是undefined的子类型。JS中的函数总会有返回,要么是一个具体的值,要么是undefined。而void和undefined在ts中有一个很大的区别在于void作为返回类型可以用不同的类型替换
function wrapper(callback: () => void) {}function test(): number {return 1;
}// works well
wrapper(test)
- never: 表示哪些用户无法达到的类型(异常)
function throwErr(): never {throw new Error("an error");
}const age = 18;throwErr();// Unreachable code detected.
age.toFixed(2);
never 还可以用于联合类型的幺元:
// string | number
type T = string | number | never;
通过在联合类型中 never 类型幺元的特性可以做到很多过滤的操作 如过滤 Human 中 age 和 name 之外的成员
type Human = {age: number;name: string;lover: string;gender: 1 | 0;
};type Filter<T> = {[P in {[K in keyof T]: K extends "age" | "name" ? K : never;}[keyof T]]: T[P];
};// type T = {
// age: number;
// name: string;
// }
type T = Filter<Human>;// 上面只是为了试验 never 的幺元特性 Filter可以更简单的写法
// type Filter<T, P> = {
// [K in Extract<keyof T, P] : T[K]
// };
// Filter<Human, 'name' | 'age'>
Keyword & Operators
typeof
一般可以使用 typeof 来进行类型保护来避免访问错误的属性或者方法
function test(b: any) {if (typeof b === "string") {// Property 'test' does not exist on type 'string'b.test();}
}
此外就是主要用来进行类型推断
const test = (a: string) => a.length;const obj = {a: 1,b: false,
};// (a: string) => number
type A = typeof test; // (x: string) => number
// {
// a: number;
// b: boolean;
// }
type B = typeof obj;
keyof
获取接口的所有键,返回键的联合类型
type Test = {a;b;
};// 'a' | 'b'
type Res = keyof Test;
in
对联合类型进行遍历
type Test = {a;b;
};// type Res = {
// a: string;
// b: string;
// }
type Res = {[K in keyof Test]: string;
};
需要注意的是只可以在 type 声明的类型下使用,如interface声明下会抛出A mapped type may not declare properties or methods.
A computed property name in an interface must refer to an expression whose type is a literal type or a ‘unique symbol’ type. The left-hand side of an arithmetic operation must be of type ‘any’, ‘number’, ‘bigint’ or an enum type. The right-hand side of an ‘in’ expression must not be a primitive.
[]
索引访问,类似于 js 的元语法
type Test1 = {a;b;
};
type Test2 = [1, 2];// any
type Res1 = Test["a"];
// 2
type Res2 = Test2[1];
这类语法可以逃脱掉类型检查的限制如调用一个没有明确声明的全局变量属性window[’undefinedVar’]
而不会获得报错,但相应的在使用的时候也不会获得任何类型提示
断言as or <>和双重断言
interface Foo {bar: number;bas: string;
}const foo = {} as Foo;
然而,如下例子中的代码将会报错,尽管使用者已经使用了类型断言:
function test(a: number): void {}// Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.(2352)
// Bad
// test('' as number);
// Good
test('' as unknown as number);
TypeScript 是怎么确定单个断言是否足够?
上面提到过ts的逆变和协变,当 S
类型是 T
类型的子集,或者 T
类型是 S
类型的子集时,S
能被成功断言成 T
。这是为了在进行类型断言时提供额外的安全性,完全毫无根据的断言是危险的,如果你想这么做,你可以借助 any
或者unknown
进行双重断言。
Class & interface
接口(Interfaces)可以用于对「对象的形状(Shape)」进行描述
实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现
- 类实现接口
- 接口继承接口
- 接口继承类(继承类型)
声明 & 模块
配置
tsconfig.json 是 ts 的编译器(tsc)将 ts 编译为 js 的配置文件,在开发和编译阶段提供支持(语法检查,代码依赖等)
使用 tsconfig.json
如果一个目录下存在一个 tsconfig.json 文件,那么它意味着这个目录是 TypeScript 项目的根目录。 tsconfig.json 文件中指定了用来编译这个项目的根文件和编译选项。 一个项目可以通过以下方式之一来编译:
- 不带任何输入文件的情况下调用 tsc,编译器会从当前目录开始去查找 tsconfig.json 文件,逐级向上搜索父目录
- 不带任何输入文件的情况下调用 tsc,且使用命令行参数–project(或-p)指定一个包含 tsconfig.json 文件的目录
当命令行上指定了输入文件时,tsconfig.json 文件会被忽略
参数:
- compilation
- …
声明
声明文件
以 .d.ts 结尾的文件用来给 ts 提供类型定义的文件 如果一个文件有扩展名 .d.ts,这意味着每个根级别的声明都必须以 declare 关键字作为前缀。这有利于让开发者清楚的知道,在这里 TypeScript 将不会把它编译成任何代码,同时开发者需要确保这些在编译时存在。
如何使用?
- 首先会寻找 package json 中 types 或 typings 指定的文件
- 然后寻找包的根目录下的 index.d.ts
- TS 官方维护的@typesDefinitelyTyped
扩展原生对象
默认的一些原生对象上是不存在一些自定义挂载的属性,所以不可以直接赋值,可以输用方括号赋值语句,但是读操作时也必须用 [] ,并且没有类型提示。
可以通过类型合并
declare interface Window {}// or
declare global {interface Window {}
}
扩展第三方库
import Vue from "vue";declare module "vue/types/vue" {interface Vue {}
}
处理其他扩展名文件
declare module "*.css" {const content: any;export default content;
}
声明合并
如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型
interface Person {age: number
}interface Person {name: string
}// Good
const man: Person = { age: 100, name: 'foo' }
模块
-
commonjs
对象
const $ = require('jquery')declare module '$' {export function forEach(callback: () => any): void
}
declare module 'koa' {function random_name(): anyexport=random_name
}
- es
export declare var age: number
//orType
declare var name: string
export { name }
// or
export default name
- global——不使用 import 或 export 的声明文件将被视为 global。顶级声明是全局导出的
- module——具有至少一个 export 声明的声明文件将被视为模块。只有 export 声明会被导出,不会定义任何 global
- 隐式 export——没有 export 声明,但使用 import 的声明文件将触发已定义但尚未说明的行为。也就是将顶级声明视为命名的 export 声明,并且不会定义 global
TS 的模块分为全局模块和文件模块,默认情况下,我们所写的代码是位于全局模块下的
// a.ts
const age = 18;
如果在另一个文件中使用 age,ts 的检查是正常的(全局)
// b.ts
console.log(age);
将当前模块变为局部的文件模块只需要当前文件存在任意的 export 或者 import 语句即可
TypeScript 团队似乎并不喜欢第三种模式,因此请尽可能避免使用第三种模式
模块解析
共有两种可用的模块解析策略:Node 和 Classic。 你可以使用 --moduleResolution 标记来指定使用哪种模块解析策略。若未指定,那么在使用了 --module AMD | System | ES2015 时的默认值为 Classic,其它情况时则为 Node。
有一个对 module 的非相对导入 import { b } from "module"
,它是在/root/src/folder/A.ts 文件里,会以如下的方式来定位"module":
Node 的模块解析通过分别查找/root/src、/root、/三种路径下的 node_modules
[/root/src/|/root/|/]node_modules/module.ts
[/root/src/|/root/|/]node_modules/module.tsx
[/root/src/|/root/|/]node_modules/module.d.ts
[/root/src/|/root/|/]node_modules/module/package.json (如果指定了"types"属性)
[/root/src/|/root/|/]node_modules/module/index.ts
[/root/src/|/root/|/]node_modules/module/index.tsx
[/root/src/|/root/|/]node_modules/module/index.d.ts
Classic 的寻址方式 这种策略在以前是 TypeScript 默认的解析策略。 现在,它存在的理由主要是为了向后兼容。
/root/src/folder/module.ts
/root/src/folder/module.d.ts
/root/src/module.ts
/root/src/module.d.ts
/root/module.ts
/root/module.d.ts
/module.ts
/module.d.ts
相关链接:
- Node
- Classic
泛型
泛指的类型,关键目的是在成员之间(类以及类成员或者函数的入参、返回值等)提供有意义的约束
interface Animal<T> {type: T
}const Jessica: Animal<'people'> = {type: 'people'
}const Wangcai: Animal<'dog'> = {type: 'dog'
}
具体泛型的使用配合下面大量的示例
Conditional Types
TypeScript 2.8 introduces conditional types which add the ability to express non-uniform type mappings. A conditional type selects one of two possible types based on a condition expressed as a type relationship test:
T extends U ? X : Y
The type above means when T is assignable to U the type is X, otherwise the type is Y.
Let’s move by a simple case
interface Human {name: stringage: numbersecondLanguage: stringlover?: Human
}const Jessica: Human = {name: 'jessica',age: 21,secondLanguage: 'Frence'
}
但不是每个人都有第二语言
const Lucia: Human = { // Type error secondLanguage is requiredname: 'jessica',age: 21,
}
但是如果要重新写一个又显的冗余重复
两种方案
- 写一个函数支持将指定属性去除(支持批量)返回去除后的新类型声明
- 写一个函数支持将指定的属性变为可选(支持批量)
去除指定属性
思路是获取去除的这个属性的 key,首先有
type TEST<T, K> = {[P in K]: T[P]
}
这个泛型 K 其实就是泛型 T 的键值,即 P extends keyof T
type TEST<T, K extends keyof T> = {[P in K]: T[P]
}
接下来需要定义一个函数来移除泛型 K 中指定的那个属性 key
type EXCLUDE<T, U> = T extends U ? never : T;
最后定义支持去除指定 key 的函数 OMIT
type OMIT<T, K extends keyof T> = {[P in EXCLUDE<keyof T, K>]: T[P]
}
再利用这个函数来声明新的 Human 类型
type Human_exclude = OMIT<Human, 'language'>const Lucia: Human = { // passname: 'jessica',age: 21,
}
将指定属性定义为可选
但是死板的去除指定属性某些场景可能不满足需求(后面可能会动态的想 TS 对象添加某个 k)
如果我们将指定的属性(language)定义为可选,那么同样满足需求
方法通用上面的移除:
- 定义去除key所对应的可选类型
- 定义去除指定的属性后的类型
- 选取1和2的交叉类型
type type OPTIONAL_K<T, K extends keyof T> = {[P in K]?: T[P];
} &OMIT<T, K>;const Lucia: Human = { // passname: 'jessica',age: 21,
}Lucia.luanguage = 'en' // pass
关于 infer
表示在 extends 条件语句中待推断的类型变量
infer
type ParamType<T> = T extends (param: infer P) => any ? P : T;
infer P 表示待推断的函数参数
如果 T 能赋值给 (param: infer P) => any
,则结果是 (param: infer P) => any
类型中的参数的类型 P,否则返回为 T
PICK
type PICK<T, K extends keyof T> = {[P in K]: T[P];
};type A = PICK<Human, "age">;
PARTIAL
type PARTIAL<T> = {[P in keyof T]?: T[P];
};type F = PARTIAL<Human>;
REQUIRED
type REQUIRED<T> = {[P in keyof T]-?: T[P];
};type G = REQUIRED<Human>;
REQUIRED_K
type REQUIRED_K<T, K extends keyof T> = {[P in K]-?: T[P];
} &OMIT<T, K>;type E = REQUIRED_K<Human, "lover">;
READONLY_RECURSIVE
type SIG = {key: {key: {key: any}}
}type READONLY_RECURSIVE<T> = {readonly [P in keyof T]: T[P] extends {[index: string]: any} ? READONLY_RECURSIVE<T[P]> : T[P];
};const test: READONLY_RECURSIVE<SIG> = {key: {key: {key: 123}}
}test.key.key = 123 // err: Cannot assign to 'key' because it is a read-only property.ts(2540)
RETURN & CONSTRUCTOR_P
type RETURN<T extends () => any> = T extends () => infer R ? R : never;type CONSTRUCTOR_P<T extends new (...args: any[]) => any> = T extends new (...args: infer P
) => any? P: any[];function bark() {return "string";
}type Eat = () => number;type BarkReturn = RETURN<typeof bark>; // string
type EatReturn = RETURN<Eat>; // numberclass HumanBeing {constructor(name: string, age: number) {}
}type HB = CONSTRUCTOR_P<typeof HumanBeing>; // [string, number]
PROMISE_LIKE
type PROMISE_LIKE<T = any> = {then<T1 = T, T2 = never>(resolve?: (value?: T) => T1 | PROMISE_LIKE<T1> | undefined | null,reject?: (value?: any) => T2 | PROMISE_LIKE<T2> | undefined | null): PROMISE_LIKE<T1 | T2>;
};
PROMISE
type PROMISE<T = any> = {then<T1 = T, T2 = never>(resolve?: (value?: T) => T1 | PROMISE_LIKE<T1> | undefined | null,reject?: (value?: any) => T2 | PROMISE_LIKE<T2> | undefined | null): PROMISE_LIKE<T1 | T2>;catch<T1 = never>(value?: any): T1 | PROMISE_LIKE<T1> | undefined | null;
};
内置类型
当你安装 TypeScript
时,会顺带安装一个 lib.d.ts
声明文件。这个文件包含 JavaScript 运行时以及 DOM 中存在各种常见的环境声明。
- 它自动包含在 TypeScript 项目的编译上下文中;
- 它能让你快速开始书写经过类型检查的 JavaScript 代码。
可以通过指定 --noLib
的编译器命令行标志(或者在 tsconfig.json
中指定选项 noLib: true
)从上下文中排除此文件。
- 上面所有示例中除却 REQUIRED_K、OPTIONAL_K 和 READONLY_RECURSIVE 之外都是属于 typescript lib.es5.d.ts 官方库帮我们内置声明好的类型,便于平时一些快捷的使用
interface PromiseLike<T>interface Promise<T>type Partial<T>type Required<T>type Readonly<T>type Pick<T, K extends keyof T>type Record<K extends keyof any, T>type Exclude<T, U>type Extract<T, U>type Omit<T, K extends keyof any>type NonNullable<T>type Parameters<T extends (...args: any) => any>type ConstructorParameters<T extends new (...args: any) => any>type ReturnType<T extends (...args: any) => any>type InstanceType<T extends new (...args: any) => any>
others
此外还内置 es5 其他函数和方法以及数据类型等的声明如果 mac vsc 有安装 typescript 拓展可以借助 vsc 打开,具体路径在/Applications/Visual Studio
Code.app/Contents/Resources/app/extensions/node_modules/typescript/lib/lib.es5.d.ts
如何利用 infer 配合协变实现高级类型声明
体操1: LeetCode 的一道 TS 面试题
https://github.com/LeetCode-OpenSource/hire/blob/master/typescript_zh.md
假设有类型
interface Action<T> {payload?: T;type: string;
}class EffectModule {count = 1;message = "hello!";delay(input: Promise<number>) {return input.then(i => ({payload: `hello ${i}!`,type: 'delay'}));}setMessage(action: Action<Date>) {return {payload: action.payload!.getMilliseconds(),type: "set-message"};}
}
经过 Connect 函数之后,返回值类型为
type Result {delay<T, U>(input: T): Action;setMessage<T, U>(action: T): Action;
}
从表面来看我们要做的有三点:
- delay<T, U>(input: Promise): Promise 变成了 asyncMethod<T, U>(input: T): Action
- setMessage<T, U>(action: Action): Action 变成了 syncMethod<T, U>(action: T): Action
- 去除了其他非函数的成员属性
step1:构造转换 1 和 2 的函数也是最关键的一步
type Transform<T> = {[K in keyof T]: T[K] extends ((input: Promise<infer P>) => Promise<{payload: infer U;type:string}>)? ((input: P) => Action<string>): T[K] extends ((action: Action<infer P>) => {payload: infer U;type:string})?((action: P) => Action<number>): never;
}type Temp = Transform<EffectModule>
// type Temp = {
// count: never;
// message: never;
// delay: (input: number) => Action<string>;
// setMessage: (action: Date) => Action<number>;
// }
step2:构造工具函数将 step1 中得到的 never 类型的无关类型去除
- 获取所有的 key
- 通过元语法批量的联合类型的 key 索引出值联合类型 value
type OmitNever<T> = {[K in keyof T]: T[K] extends Function ? never: K
}[keyof T];type temp = OmitNever<EffectModule>
// type temp = "count" | "message"
step3: 借助 Omit 移除 step1 中 step2 的 key
type Connect<T> = Omit<Transform<T>, OmitNever<T>>;type Result = Connect<EffectModule>
// type Result = {
// delay: (input: number) => Action<string>;
// setMessage: (action: Date) => Action<number>;
// }
体操2: 联合类型转交叉类型:A | B => A & B
type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I,
) => void)? I: never
- 如果 extends 左边是联合类型,那么 TS 会把 extends 操作符左边的联合类型拆开做判断,这样就得到了很多个函数的联合。如果第一部分的输入是 A | B 那么输出是
((k: A) => void) | ((k: B) => void)
而不是(k: A|B) => void
- 如果左边是一个函数,那就把它第一个参数的类型拿出来返回
为什么 ((k: A) => void) | ((k: B) => void)
的参数是 A & B?
因为函数参数是逆变的,我们假设有一个变量能同时传给 (k: A) => void 和 (k: B) => void
,那么这个变量的类型应该是 A & B 而不是 A | B
P extends K 意味着所有 K 都可以无条件被 P 替换
一个函数能被 (k: A) => void 和 (k: B) => void
无条件替换,那么那个函数接受的参数必然既是 A 又是 B
- Q: 根据 conditional type ***
((k: A) => void) | ((k: B) => void)
*不是应该被分开处理吗?如果分开处理那得到的结果依然会是 A | B,那么又是为什么能够得出 A & B 呢?
conditional-type
extends 左边联合类型被拆开判断的情况只会出现在左边是一个类型参数的情况:大概就是 type F = T extends any 的这个左边是会被拆开,而 type F = A | B extends any 的左边就不会被拆开。
type Union = {age} | {number}type Union2 = number | stringtype Intersection = UnionToIntersection<Union> // {age} & {number}type Intersection2 = UnionToIntersection<Union2> // never
体操3: Closing note: 联合类型转 TUPLE:A | B => [A, B]
主体的思路是递归的将联合类型的每一项取出来放入元组同时移除这一项,最后将递归结束后的元组返回;同时也要写一些基本的辅助函数:
- 借助协变,联合类型转交叉类型
- 每次递归将放入元组的那个类型从原集合获取
- 每次递归获取放入元组的单个类型的 prepend 函数
- 移除每次递归推入元组的那个类型
- 借助函数入参 reset 通过 infer 推断出元祖
step1: 将 Union 类型转为由函数类型组成的交叉类型
// union to intersection of functions
// 42 =====> (42) => void =====> ((a: U, ...r: T) infer R
type UnionToIoF =(U extends any ? (k: (x: U) => void) => void : never) extends((k: infer I) => void) ? I : never
step2: 借助特性每次获取最后一个元素
type UnionPop = UnionToIoF extends (a: infer A) => void ? A : never;
step3: 将获取的类型推入元组
type Prepend<U, T extends any[]> =((a: U, ...r: T) => void) extends (...r: infer R) => void ? R : never;
step4: 移除推入的类型
// 借助内置类型 Exclude
type Exclude<T, U> = T extends U ? never : T;
step5: 最后一步借助上面的工具函数写转换的递归
type UnionToTupleRecursively<Union, Result extends any[]> = {0: Result;1: UnionToTupleRecursively<Exclude<Union, UnionPop<Union>>, Prepend<UnionPop<Union>, Result>>
}[[Union] extends [never] ? 0 : 1];
Q: 此处为何要借助 {}[] 的形式来作为递归的终止条件而不是直接使用三目呢?
type aliases are not like interfaces. interfaces are named types, where as type aliases are just aliases. internally as well they are treated differently, the compiler aggressively flatten types aliases to their declarations.
type alias 不允许调用自身 这里使用索引方式 {}[]
Ts 4.1.3 版本后取消此限制
// wrong
type UnionToTupleRecursively<Union, Result extends any[]> = [Union] extends [never] ? Result : UnionToTupleRecursively<Exclude<Union, UnionPop<Union>>, Prepend<UnionPop<Union>, Result>>
Below is the complete code.
type UnionToIoF =(U extends any ? (k: (x: U) => void) => void : never) extends((k: infer I) => void) ? I : never// return last element from Union
type UnionPop = UnionToIoF extends (a: infer A) => void ? A : never;// prepend an element to a tuple.
type Prepend<U, T extends any[]> =((a: U, ...r: T) => void) extends (...r: infer R) => void ? R : never;type UnionToTupleRecursively<Union, Result extends any[]> = {0: Result;1: UnionToTupleRecursively<Exclude<Union, UnionPop<Union>>, Prepend<UnionPop<Union>, Result>>
}[[Union] extends [never] ? 0 : 1];type UnionToTuple = UnionToTupleRecursively<U, []>;// support union size of 43 at most
type Union43 = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |40 | 41 | 42 | 43;type A_ = UnionPop<Union43>
// type A_ = 43
type B_ = UnionToIoF<Union43>
// type B_ = ((x: 1) => void) & ((x: 2) => void) & ((x: 3) => void) & ((x: 4) => void) & ((x: 5) => void) & ((x: 6) => void) & ((x: 7) => void) & ((x: 8) => void) & ((x: 9) => void) & ((x: 10) => void) & ((x: 11) => void) & ((x: 12) => void) & ((x: 13) => void) & ((x: 14) => void) & ((x: 15) => void) & ((x: 16) => void) & ... 26 more ... & ((x: 43) => void)type UnionTest = string | number | 'sss' | {age:123}
type TupleTest = UnionToTuple<UnionTest>;
// type TupleTest = [a: string, a: number, a: {
// age: 123;
// }]type Tuple = UnionToTuple<Union43>;
// type Tuple = [a: 1, a: 2, a: 3, a: 4, a: 5, a: 6, a: 7, a: 8, a: 9, a: 10, a: 11, a: 12, a: 13, a: 14, a: 15, a: 16, a: 17, a: 18, a: 19, a: 20, a: 21, a: 22, a: 23, a: 24, a: 25, a: 26, a: 27, a: 28, a: 29, a: 30, a: 31, a: 32, a: 33, a: 34, a: 35, a: 36, a: 37, a: 38, a: 39, a: 40, a: 41, a: 42, a: 43]
一个有意思的是批量处理联合类型时支持的最大长度为 43(没确定是否和版本有关),否则会抛出 Type instantiation is excessively deep and possibly infinite.ts(2589) 的异常
Ts 4.1.3 版本后取消此限制
Extends
除却本次分享之外 TS 还有很多其他相关的很多特性包括
- 类型保护 typeof/instanceof/in
- Flow type 自动更新
- Freshness 严格的字面量类型检查
- 异常处理 Error/RangeError/RangeError…
- 兼容
- …
以及更多高阶运用
编译原理
- AST
- Scanner(
scanner.ts
) - Parser(
parser.ts
) - Binder(
binder.ts
) - Checker(
checker.ts
)
最后
做一个合格的Typescript工程师,感受体操的魅力!
从type-challenges开始:
https://github.com/type-challenges/type-challenges
相关文章:

Ts入门到放弃
TS 的核心能力在于给 JS 提供静态类型检查,是有类型定义的 JS 的超集,包括 ES5、ES5 和其他一些诸如泛型、类型定义、命名空间等特征的集合。 本次仅会针对类型声明部分配合示例进行着重介绍,更详细的内容以及特性可以查看 Typescript handb…...

黑客技术(网络安全)学习笔记
一、网络安全基础知识 1.计算机基础知识 了解了计算机的硬件、软件、操作系统和网络结构等基础知识,可以帮助您更好地理解网络安全的概念和技术。 2.网络基础知识 了解了网络的结构、协议、服务和安全问题,可以帮助您更好地解决网络安全的原理和技术…...

Cloud Kernel SIG 月度动态:支持龙芯和申威架构,合入两个内存新特性
Cloud Kernel SIG(Special Interest Group):支撑龙蜥内核版本的研发、发布和服务,提供生产可用的高性价比内核产品。 01 SIG 整体进展 Cloud Kernel 开始支持龙芯和申威架构。 合入两个内存新特性:MEMCG LRU LOCK 和…...

IDEA中连接虚拟机 管理Docker
IDEA中连接虚拟机 管理Docker 📔 千寻简笔记介绍 千寻简笔记已开源,Gitee与GitHub搜索chihiro-notes,包含笔记源文件.md,以及PDF版本方便阅读,且是用了精美主题,阅读体验更佳,如果文章对你有帮…...

Debezium日常分享系列之:定制Debezium 信号发送和通知
Debezium日常分享系列之:定制Debezium 信号发送和通知 一、自定义信号和通知通道二、结论 Debezium 2.3 在信号和通知功能方面引入了新的改进。除了 Debezium 提供的预定义信号和通知通道之外,您还可以设置新的信号和通知通道。此功能使用户能够自定义系…...

RpcProvider(rpc服务提供者)实现思路
RpcProvider(服务提供者)实现思路 上一节说到,如何将一个本地服务发布成远程服务,但没有说明一个rpc框架怎么进行调用的,看看上节代码 #include <iostream> #include <string> #include "user.pb.h…...

GNSS技术知识你知道多少?这些你或许还未掌握
GNSS信号频段 GNSS频谱图展示了不同的GNSS信号及其星座、载波频率、调制方案,以及所有这些信号在同一L波段频段内如何相互关联,是GNSS专业人员的必备工具,包括设计和开发GNSS系统的工程师,以及测试GNSS系统的工程师。 GNSS术语 …...

YOLOv8教程系列:三、使用YOLOv8模型进行自定义数据集半自动标注
YOLOv8半自动标注 目标检测半自动标注的优点包括: 1.提高标注效率:算法能够自动标注部分数据,减少了人工标注的工作量,节省时间和资源。 2.降低成本:自动标注可以减少人工标注的成本,特别是对于大规模数据…...

AI聊天GPT三步上篮!
1、是什么? CHATGPT是OpenAI开发的基于GPT(Generative Pre-trained Transformer)架构的聊天型人工智能模型。也就是你问它答,根据网络抓去训练 2、怎么用? 清晰表达自己诉求,因为它就是一个AI助手&#…...

如何彻底卸载VMware
目录 第一章、停止并卸载VMware程序1.1)停止VMware有关的服务1.2)打开任务管理器停止进程1.3)卸载VMware程序 第二章、残留文件删除2.1)打开注册表2.2)删除注册表残留文件2.3)C盘文件删除 友情提醒…...

[个人笔记] Windows配置NTP时间同步
Windows - 运维篇 第六章 Windows配置NTP时间同步 Windows - 运维篇系列文章回顾Windows配置NTP时间同步域控环境的NTP配置工作组环境的NTP配置Windows的CMD部分命令集 参考来源 系列文章回顾 第一章 迁移WinSrv系统到虚拟机 第二章 本地安全策略xcopy实现实时备份文件夹内容 …...

Jetson Docker 编译 FFmpeg 支持硬解nvmpi和cuvid
0 设备和docker信息 设备为NVIDIA Jetson Xavier NX,jetpack版本为 5.1.1 [L4T 35.3.1] 使用的docker镜像为nvcr.io/nvidia/l4t-ml:r35.2.1-py3,详见https://catalog.ngc.nvidia.com/orgs/nvidia/containers/l4t-ml 使用下列命令拉取镜像: sudo docker pull nvcr…...

某某某小说app接口抓包分析
详细说明查看原文 https://sdk.qzbonline.com/ver9/shuhuajs/sdk/ioszh_shuhuajs_conf.htmlhttps://sdk.qzbonline.com/prov8/ymqxs/sdk/ios_ymqxs_conf.htmlhttps://sdk.qzbonline.com/prov8/ymqxs/sdk/ios_ymqxs_conf2.htmlhttps://sdk.qzbonline.com/prov8/fqhyxs/sdk/iosz…...

开发一个RISC-V上的操作系统(四)—— 内存管理
目录 往期文章传送门 一、内存管理简介 二、Linker Script 链接脚本 三、动态分配内存 四、测试 往期文章传送门 开发一个RISC-V上的操作系统(一)—— 环境搭建_riscv开发环境_Patarw_Li的博客-CSDN博客 开发一个RISC-V上的操作系统(二…...

区块链:可验证随机函数
本篇主要介绍可验证随机函数的定义及其在区块链上的作用。 1 可验证随机函数 1.1 定义 可验证随机函数(Verifiable Random Function,VRF)本质上还是一类具有验证功能的伪随机函数。对于一个特定的输入 m m m以及输入者的私钥 S K SK SK,VRF会输出一个随…...

Flask中flask-session
Flask中flask-session Flask-Session是一个为Flask应用程序开发的工具,允许您轻松处理服务器端会话。会话是存储和追踪用户特定数据的方式。例如,当用户登录到应用程序时,他们的状态(即登录状态)可以保存在会话中&…...

react-Native init初始化项目报错”TypeError: cli.init is not a function“
文章目录 一、问题:二、解决: 一、问题: 在react-native init appDemo 创建项目时,报错TypeError: cli.init is not a function。 二、解决: 产生这个问题的原因是:使用这种方式创建工程,rea…...

【gitlib】linux系统rpm安装gitlib最新版本
目录 下载gitlib安装包 安装需要的依赖 设置开机启动 安装邮件服务器并设置开机启动 rpm执行安装gitlib 修改gitlib.rb文件的属性 修改完毕后执行更新配置 查看gitlib运行 查看gitlib初始化root密码 gitlib入口访问地址 下载gitlib安装包 Index of /gitlab-ce/yum/el7/…...

iOS开发-检查版本更新与强制更新控制
iOS开发-检查版本更新与强制更新控制。 在开发中经常遇到需要检查版本,检查版本及请求appstoreLookUrl查看版本号与当前的版本号进行比对,看是否需要更新。强制更新控制,是将获取到当前版本号传给服务端,服务端判断当前的版本是否…...

自动化运维工具——Ansible
自动化运维工具——Ansible 一、Ansible概述二、ansible 环境安装部署1.管理端安装 ansible2.ansible 目录结构3.配置主机清单4.配置密钥对验证 三、ansible 命令行模块1.command 模块2.shell 模块3.cron 模块4.user 模块5.group 模块6.copy 模块7.file 模块8.hostname 模块9&a…...

W2NER详解
论文:https://arxiv.org/pdf/2112.10070.pdf 代码:https://github.com/ljynlp/W2NER 文章目录 W2NER介绍模型架构解码 源码介绍数据输入格式模型代码 参考资料 W2NER 介绍 W2NER模型,将NER任务转化预测word-word(备注ÿ…...

ElementUI tabs标签页样式改造美化
今天针对ElementUI的Tabs标签页进行了样式修改,更改为如下图所属的样子。 在线运行地址:JSRUN项目-ElementUI tabs标签页样式改造 大家如果有需要可以拿来修改使用,下面我也简单的贴上代码,代码没有注释,很抱歉&#x…...
出海周报|Temu在美状告shein、ChatGPT安卓版上线、小红书回应闪退
工程机械产业“出海”成绩喜人,山东相关企业全国最多Temu在美状告shein,跨境电商战事升级TikTok将在美国推出电子商务计划,售卖中国商品高德即将上线国际图服务,初期即可覆盖全球超200个国家和地区ChatGPT安卓版正式上线ÿ…...

2023年7月26日 单例模式
单例模式 饿汉模式 package com.wz.cinema.platform.server.util;public class DataManager {/*** 单例模式:整个类在运行中只会有一个实例* 既然是在运行中只有一个实例,那么就必须* 考虑多线程环境** 单例模式分为懒汉模式和饿汉模式* 饿汉模式本身就是…...

[ 容器 ] Docker 安全及日志管理
目录 Docker 容器与虚拟机的区别Docker 存在的安全问题Docker 架构缺陷与安全机制Docker 安全基线标准容器相关的常用安全配置方法限制流量流向镜像安全避免Docker 容器中信息泄露DockerClient 端与 DockerDaemon 的通信安全 容器的安全性问题的根源在于容器和宿主机共享内核。…...

游游的排列构造
示例1 输入 5 2 输出 3 1 5 2 4 示例2 输入 5 3 输出 2 1 4 3 5 #include<bits/stdc.h> using namespace std; typedef long long ll; const int N1e55; int n,k; int main(){scanf("%d%d",&n,&k);int xn-k1;int yn-k;int f1;for(int i1;i&l…...

拯救者Y9000K无线Wi-Fi有时不稳定?该如何解决?
由于不同品牌路由器的性能差异,无法完美兼容最新的无线网卡技术,在连接网络时(特别是网络负载较大的情况下),可能会出现Wi-Fi信号断开、无法网络无法访问、延迟突然变大的情况;可尝试下面方法进行调整。 1…...

【业务功能篇59】Springboot + Spring Security 权限管理 【下篇】
UserDetails接口定义了以下方法: getAuthorities(): 返回用户被授予的权限集合。这个方法返回的是一个集合类型,其中每个元素都是一个GrantedAuthority对象,表示用户被授予的权限。getPassword(): 返回用户的密码。这个方法返回的是一个字符…...

性能优化 - 前端性能监控和性能指标计算方式
性能优化 - 前端性能监控和性能指标计算方式 前言一. 性能指标介绍1.1 单一指标介绍1.2 指标计算① Redirect(重定向耗时)② AppCache(应用程序缓存的DNS解析)③ DNS(DNS解析耗时)④ TCP(TCP连接耗时)⑤ TTFB(请求响应耗时)⑥ Trans(内容传输耗时)⑦ DOM(DOM解析耗时) 1.3 FP(f…...
git stash clear清空本地暂存代码
git stash clear清空本地暂存代码 git stash 或者 git stash list 查看本地暂存的代码。 清除本地暂存的代码修改: git stash clear git回退代码仓库版本_git回退到之前的版本会影响本地代码嘛_zhangphil的博客-CSDN博客git回退代码版本_git回退到之前的版本会影…...