TypeScript
https://jkchao.github.io/typescript-book-chinese/
类型注解:TypeAnnotation
泛型 约束类型一致,而不是约定某种类型,例如输入输出一致
联合类型 string | number
交叉类型 T & U
元组 :[typeofmember1, typeofmember2]
类型别名 type SomeName = someValidTypeAnnotation
declare
interface ReturnString {
(): string;
new (): string;
}
// 'const' declarations must be initialized
const foo1: ReturnString;
// 'ReturnString' only refers to a type, but is being used as a value here
const foo2: ReturnString = ReturnString();
declare const foo: ReturnString;
const bar = foo(); // bar 被推断为一个字符串。
declare const Two: ReturnString;
const two = new Two(); // two 被推断为 string 类
函数做参数,函数参数个数不约束
type F = (err: Error, data: any) => void
let a: F = function (a) { } // 参数数量可以少
枚举
本质
enum Color {
red,
blue,
green
}
const Color = {
"0": "red",
"1": "blue",
"2": "green",
"red": 0,
"blue": 1,
"green": 2
}
编译前
enum Enum {
A,
B,
C = "C",
D = "D",
E = 8,
F,
}
编译后
"use strict";
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
Enum[Enum["B"] = 1] = "B";
Enum["C"] = "C";
Enum["D"] = "D";
Enum[Enum["E"] = 8] = "E";
Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));
字面量类型
// let 不行,如果是 let foo 类型是 number
const foo = 123;
let bar: typeof foo; // 'bar' 类型与 'foo' 类型相同(在这里是:123)
bar = 123; // ok
bar = 789; // Type '789' is not assignable to type '123'
never
- void 表示没有任何类型
- never 表示永远不存在的值的类型
function fn(): never {
// 如果是个死循环,则应该是个 never
while (true) { }
}
function fn1() {
throw new Error();
}
const fn2 = () => { throw new Error() }
let a: never = fn();
a = fn1() // 函数声明不能识别为 never
a = fn2() // 函数表达式(字面量)可以识别为 never
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === "number") {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
}
如果 Foo 变为type Foo = string | number | boolean;
在 else 中,foo 就是 boolean 类型,编译就会报错
索引签名
- 属性和索引签名不要同时使用,如果需要应该使用嵌套
interface Bar {
[key: string]: number;
x: number;
y: string; // Error: y 属性必须为 number 类型
}
type Index = "a" | "b" | "c";
type FromIndex = { [k in Index]?: number };
type FromSomeIndex<K extends string> = { [key in K]: number };
// 同时拥有 string 和 number 类型的索引签名
interface ArrStr {
[key: string]: string | number; // 必须包括所用成员类型
[index: number]: string; // 字符串索引类型的子级
// example
length: number;
}
type FieldState = {
value: string;
};
type FormState = { isValid: boolean } & { [fieldName: string]: FieldState };
// type FromState = {
// isValid: boolean; // Error: 不符合索引签名
// [filedName: string]: FieldState;
// };
// 将它用于从某些地方获取的 JavaScript 对象
declare const foo: FormState;
const isValidBool = foo.isValid;
const somethingFieldState = foo["something"];
// 使用它来创建一个对象时,将不会工作
const bar: FormState = {
// 'isValid' 不能赋值给 'FieldState'
isValid: false, // 这里的 isValid 要么符合属性,要么符合签名,不能都符合,所以报错
// isValid: { value: "" },
};
捕获键的名称 keyof
const colors = {
red: 'red',
blue: 'blue'
};
type Colors = keyof typeof colors;
let color: Colors; // color 的类型是 'red' | 'blue'
color = 'red'; // ok
color = 'blue'; // ok
color = 'anythingElse'; // Error
混合
// 所有 mixins 都需要
type Constructor<T = {}> = new (...args: any[]) => T;
// 添加属性的混合例子
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
timestamp = Date.now();
};
}
class User {
name = "";
}
// 添加 Timestamped 的 User
const TimestampedUser = Timestamped(User);
// 这里可以实现相功能,但 User 是写死的
// TimestampedUser 中的 TBase 是活的
// 多个类型需要增加属性时,混合的优势就出来的
class TimestampedUser1 extends User {
timestamp = Date.now();
}
const timestampedUserExample = new TimestampedUser();
console.log(timestampedUserExample.timestamp);
const timestampedUserExample1 = new TimestampedUser1();
console.log(timestampedUserExample1.timestamp);
类型断言 <>
as
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// let strLength: number = (someValue as string).length;
// 适合使用
function handler(event: Event) {
const mouseEvent = event as MouseEvent;
}
interface Foo {
bar: number;
bas: string;
}
// 无法提示错误
const foo = {
bar: 1,
} as Foo;
// Property 'bas' is missing in type '{ bar: number; }' but required in type 'Foo'.
const foo1: Foo = {
bar: 1,
};
<>
与 JSX 的语法存在歧义,建议 as- 编译时语法,不是类型转换
- 应避免使用类型断言
类型守卫
lodash isEmpty 的例子:https://github.com/DefinitelyTyped/DefinitelyTyped/pull/60401
in
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
if ("privileges" in emp) {
}
if ("startDate" in emp) {
}
}
typeof
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
}
if (typeof padding === "string") {
}
}
instanceof
interface Padder {
getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}
let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
// padder 的类型收窄为 'SpaceRepeatingPadder'
}
自定义类型保护的类型谓词
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
可辨识联合
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
let s: Shape = { size: 2, kind: 'square' }
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}
kind 就为该类型的辨识
接口
任意属性
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
const p1 = { name: "semlinker" };
const p2 = { name: "lolo", age: 5 };
const p3 = { name: "kakuqo", sex: 1 }
接口与类型别名区别
//接口
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
//类型别名
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
区别
// primitive
type Name = string;
// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
// union
type PartialPoint = PartialPointX | PartialPointY;
// tuple
type Data = [number, string];
infer
表示在 extends 条件语句中待推断的类型变量。
- 用于提取函数类型的返回值类型
- 用于提取构造函数中参数(实例)类型
type ParamType<T> = T extends (arg: infer P) => any ? P : T;
type Func = (a: string) => void;
type Func1 = (a: string, b: number) => void;
type Param = ParamType<Func>; // Param = string
type Param1 = ParamType<Func1>; // Param1 = (a: string, b: number) => void
type Param2 = ParamType<string>; // Param2 = string
装饰器
类装饰器
import "reflect-metadata";
const formatMetadataKey = Symbol("format");
@sealed
class BugReport {
@format("Hello world, %s")
private _x: string;
title: string;
@Prop()
public Aprop!: string;
constructor(title: string) {
this._x = title;
this.title = title;
}
@configurable(true)
get x() {
return 'get,' + this._x;
}
@enumerable(true)
greet() {
return "Hello, " + this.title;
}
@Metadata1()
metadataFun(a: number, b: number): number {
return a + b;
}
greetFormat() {
let formatString = getFormat(this, "_x");
return formatString.replace("%s", this._x);
}
}
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// 类中方法 enumerable 默认为 false 不可枚举
descriptor.enumerable = value;
};
}
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
function Prop() {
return (target: any, key: string) => {
const type = Reflect.getMetadata('design:type', target, key);
console.log(`${key} type: ${type.name}`);
};
}
function Metadata1() {
return (target: any, key: string) => {
const type = Reflect.getMetadata('design:type', target, key);
const paramtypes = Reflect.getMetadata('design:paramtypes', target, key);
const returntype = Reflect.getMetadata('design:returntype', target, key);
console.log(key, type.name, paramtypes.map((c: any) => c.name), returntype.name);
};
}
const br = new BugReport('title');
// test @sealed
// Cannot add property title, object is not extensible
// BugReport.prototype.title = 'title';
// test @configurable
// 与 @sealed 有冲突
// console.log('delete x before', br.x);
// // delete 操作只会在自身的属性上起作用,即 delete br.x 无效
// delete BugReport.prototype.x
// console.log('delete x after', br.x)
// test @enumerable
// for (const a in br) {
// console.log(a)
// }
console.log(br.greetFormat())
控制反转 依赖注入
import "reflect-metadata";
type Constructor<T = any> = new (...args: any[]) => T;
// 相当于一个空的类装饰器
// 用于标记一下 TestService
const Injectable = (): ClassDecorator => target => { };
class OtherService {
a = 1;
}
@Injectable()
class TestService {
constructor(public readonly otherService: OtherService) { }
testMethod() {
console.log(this.otherService.a);
}
}
function Factory<T>(target: Constructor<T>):T{
// 获取所有注入的服务
const providers = Reflect.getMetadata('design:paramtypes', target); // [OtherService]
console.log(providers)
const args = providers.map((provider: Constructor) => new provider());
return new target(...args);
}
Factory(TestService).testMethod(); // 1
covariance and contravariance
class Animal {
hx() {
console.log('呼吸')
}
}
class Dog extends Animal {
gj() {
console.log('狗叫')
}
}
class Greyhound extends Dog {
color = "grey"
}
class GermanShepherd extends Dog {
name = 'GermanShepherd'
}
type G = (dog: Dog) => Dog
function fun(g: G) {
const r = g(new Dog())
r.gj()
}
function greyhoundToGreyhound(greyhound: Greyhound) {
return greyhound
}
function greyhoundToAnimal(greyhound: Greyhound) {
return greyhound as Animal
}
function animalToAnimal(an: Animal) {
return an
}
function animalToGreyhound(an: Animal) {
return an as Greyhound
}
// Property 'color' is missing in type 'Dog' but required in type 'Greyhound'.
fun(greyhoundToGreyhound)
// Type 'Dog' is not assignable to type 'Greyhound'.
fun(greyhoundToAnimal)
// Property 'gj' is missing in type 'Animal' but required in type 'Dog'.
fun(animalToAnimal)
// ok
fun(animalToGreyhound)
https://www.stephanboyer.com/post/132/what-are-covariance-and-contravariance
- 返回值类型是协变的
A ≼ B 就意味着 (T → A) ≼ (T → B)
- 而参数类型是逆变的
A ≼ B 就意味着 (B → T) ≼ (A → T)
type interface
不同点:
- type 可用于 string、number、bool、undefined、null,而 interface 只能描述对象(含数组、函数、包装对象、元组)
- 同名 interface 会合并,而同名 type 会报错
- type 声明的是类型别名,而 interface 声明的是新类型。
相同点:
- 都能描述对象(含数组、函数、包装对象)
- 都能用于扩展一个类型。type 用交叉类型做到这一点,interface 用 extends 做到这一点。
type-challenges
Equal
- 捏造一个 T
export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false;
Pick
type MyPick<T, K extends keyof T> = { [p in K]: T[p] }
TupleToObject
type TupleToObject<T extends readonly any[]> = {
[k in T[number]]: k
}
First & Last & Pop
- infer
type First<T extends any[]> = T extends [infer F, ...any] ? F : never
type Last<T extends any[]> = T extends [...any, infer Y] ? Y : never
type Pop<T extends any[]> = T extends [...infer X, any] ? X : never
Exclude
type MyExclude<T, U> = T extends U ? never : T
recursion infer
type MyAwaited<T> = T extends Promise<infer F> ? MyAwaited<F> : T
If
type If<C extends boolean, T, F> = C extends true ? T : F
Includes
type Includes<T extends readonly any[], U> = T extends [infer F, ...infer R]
? Equal<F, U> extends true
? true
: Includes<R, U>
: false;
MyParameters
type MyParameters<T extends (...args: any) => any> = T extends (...args: infer X) => any ? X : never;
MyReturnType
type MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer X ? X : never
MyOmit
type MyOmit<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P]
}
MyReadonly2
type MyReadonly2<T, K extends keyof T = keyof T> = {
readonly [P in K]: T[P]
} & { [P in Exclude<keyof T, K>]: T[P] }
DeepReadonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends Function
? T[K]
: T[K] extends object
? DeepReadonly<T[K]>
: T[K]
}
TupleToUnion
type TupleToUnion<T extends any[]> = T[number]
Chainable
type Chainable<T = {}> = {
option<K extends string, P>(
key: K extends keyof T
? (Equal<P, T[K]> extends true ? never : K)
: K,
value: P
): Chainable<Omit<T, K> & Record<K, P>>
get(): T
}
- T = {} 来作为默认值 用来存储
- K 必须要,要判断重复的 key
- key 重复,且 value 类型形同,则 never
- Omit 排除同名 key,即同名 key 的覆盖
PromiseAll
declare function PromiseAll<T extends any[]>(
value: readonly [...T]
): Promise<{ [P in keyof T]: T[P] extends Promise<infer R> ? R : T[P] }>
TrimLeft Trim
type Blank = ' ' | '\n' | '\t';
type TrimLeft<S extends string> = S extends `${Blank}${infer Tail}` ? TrimLeft<Tail> : S
type Trim<S extends string> = S extends `${Blank}${infer Tail}` ? Trim<Tail> : S extends `${infer Tail}${Blank}` ? Trim<Tail> : S
MyCapitalize
type MyCapitalize<S extends string> = S extends `${infer F}${infer Rest}` ? `${Uppercase<F>}${Rest}` : S;
Replace & ReplaceAll
type Replace<S extends string, From extends string, To extends string> = S extends `${infer L}${From}${infer R}` ? `${L}${From extends '' ? '' : To}${R}` : S
type ReplaceAll<S extends string, From extends string, To extends string> = From extends ''
? S
: S extends `${infer L}${From}${infer R}`
? `${ReplaceAll<L, From, To>}${To}${ReplaceAll<R, From, To>}`
: S;
AppendArgument
type AppendArgument<Fn, A> = Fn extends (...args: infer P) => infer R ? (...args: [...P, A]) => R : never
Permutation
// T extends U 中的 T 如果是一个联合类型,如:A | B | C,则这个表达式会被展开成
// (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
// [U] extends [never] 而不是 U extends never 因为 U 是联合类型 条件类型会走分配得到的是一个联合类型 不符合期望
type Permutation<T, U = T> = [U] extends [never] ? [] : (T extends U ? [T, ...Permutation<Exclude<U, T>>] : [])
LengthOfString
type LengthOfString<S extends string, A extends any[] = []> =
S extends `${infer R}${infer U}`
? LengthOfString<U, [...A, R]>
: A['length']
Flatten
type Flatten<T> = T extends [infer F, ...infer Rest] ? [...(F extends unknown[] ? Flatten<F> : [F]), ...Flatten<Rest>] : []
AppendToObject
type AppendToObject<T, U extends string | number | symbol, V> = {
[K in keyof T | U]: K extends keyof T ? T[K] : V;
}
Absolute
type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer R}` ? `${R}` : `${T}`
StringToUnion
type StringToUnion<T extends string> = T extends `${infer F}${infer R}` ? F | StringToUnion<R> : never
Merge Diff
type Merge<F, S> = {
[K in keyof (S & F)]: K extends keyof S ? S[K] : K extends keyof F ? F[K] : never
}
type Diff<F, S> = Omit<F & S, keyof F & keyof S>
KebabCase
// Uncapitalize:第一个字符转换为小写字母
type KebabCase<T extends string> = T extends `${infer First}${infer Rest}`
? Rest extends Uncapitalize<Rest>
? `${Lowercase<First>}${KebabCase<Rest>}`
: `${Lowercase<First>}-${KebabCase<Rest>}`
: T;
// KebabCase<'FooBarBaz'> -> 'foo-bar-baz'
AnyOf
type AnyOf<T extends readonly any[]> = T extends Array<0 | '' | false | [] | Record<string, never>> ? false : true
IsNever
type IsNever<T> = [T] extends [never] ? true : false
IsUnion
- 泛型为联合类型时进行分发处理
todo: 为何要 copy B
type IsUnion<A, B = A> = [A] extends [never] ? false : (
A extends A ? (
[B] extends [A] ? false : true
) : false
)
ReplaceKeys
type ReplaceKeys<U, T, Y> = {
[K in keyof U]: K extends T ? K extends keyof Y ? Y[K] : never : U[K]
}
RemoveIndexSignature
type RemoveIndexSignature<T> = {
// 排除 number symbol number
[K in keyof T as (number extends K ? never : symbol extends K ? never : string extends K ? never : K)]: T[K]
}
PercentageParser
type Parser1<A> = A extends `${infer F}${string}` ? F extends '+' | '-' ? F : '' : ''
type Parser2<A> = A extends `${string}%` ? '%' : ''
type Parser3<A> = A extends `${Parser1<A>}${infer M}${Parser2<A>}` ? M : ''
type PercentageParser<A> = [Parser1<A>, Parser3<A>, Parser2<A>]
DropChar
type DropChar<S, C> = S extends `${infer F}${infer R}` ? `${F extends C ? '' : F}${DropChar<R, C>}` : S;
MinusOne
type MinusOne<T extends number, C extends any[] = []> = [1, ...C]['length'] extends T
? C['length']
: [1, 1, ...C]['length'] extends T // reduce recurtion depth
? [1, ...C]['length']
: MinusOne<T, [1, 1, ...C]>;
PickByType & OmitByType
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
}
type OmitByType<T, U> = {
[K in keyof T as T[K] extends U ? never : K]: T[K]
}
StartsWith & EndsWith
type StartsWith<T extends string, U extends string> = T extends `${U}${string}` ? true : false
type EndsWith<T extends string, U extends string> = T extends `${string}${U}` ? true : false
PartialByKeys & RequiredByKeys
- -? 必选项
- +? 可选项
type PartialByKeys<T, K = any> = Merge<{
[P in keyof T as P extends K ? P : never]?: T[P];
} & {
[P in keyof T as P extends K ? never : P]: T[P];
}>;
type RequiredByKeys<T, K = any> = Merge<{
[P in keyof T as P extends K ? P : never]-?: T[P];
} & {
[P in keyof T as P extends K ? never : P]: T[P];
}>;
type Merge<T> = {
[K in keyof T]: T[K];
}
Mutable
type Mutable<T> = {
- readonly [K in keyof T]: T[K]
}
ObjectEntries
// https://github.com/type-challenges/type-challenges/issues/15350
type NeverToUndefined<T> = [T] extends [never] ? undefined : T
type ObjectEntries<T, S extends keyof T = keyof T> = S extends S ? [S, NeverToUndefined<Required<T>[S]>] : never
Shift
type Shift<T> = T extends [infer _, ...infer R] ? R : T
TupleToNestedObject
type TupleToNestedObject<T, U> = T extends [
infer F extends PropertyKey,
...infer R
]
? Record<F, TupleToNestedObject<R, U>>
: U;
Reverse
type Reverse<T> = T extends [...infer F, infer E] ? [E, ...Reverse<F>] : T
FlipArguments
type Reverse<T> = T extends [...infer F, infer E] ? [E, ...Reverse<F>] : T
type FlipArguments<T> = T extends (...args: infer F) => infer R ? (...args: Reverse<F>) => R : T
FlattenDepth
type FlattenDepth<T, Depth extends number = 1, U extends any[] = []> = T extends [infer F, ...infer R]
? F extends any[]
? U["length"] extends Depth
? [F, ...FlattenDepth<R, Depth, U>]
: [...FlattenDepth<F, Depth, [any, ...U]>, ...FlattenDepth<R, Depth, U>]
: [F, ...FlattenDepth<R, Depth, U>]
: T;
TODO
as const
declare
d.ts
implements 与 extends
接口主要强调结构
泛型提供约束
suppressImplicitAnyIndexErrors: 禁止 TypeScript 在使用动态属性访问时报告隐式 any 类型错误