sufuwang / demo

My study note about technology
0 stars 0 forks source link

TypeScript #6

Open sufuwang opened 1 year ago

sufuwang commented 1 year ago

学习一下 ts 零碎的知识点

sufuwang commented 1 year ago

TypeScript 内置类型

TypeScript Playground

Parameters

type Func = (obj: 1, other: 'other') => void

type MyParameters<Fn> = Fn extends (...args: infer T) => any ? T : any

// [obj: 1, other: "other"]
type P1 = Parameters<Func>
type P2 = MyParameters<Func>

ReturnType

type Func = (obj: 1, other: 'other') => [1, 'A']

type MyReturnType<Fn> = Fn extends (...args: any) => infer T ? T : any

// [1, "A"]
type P1 = ReturnType<Func>
type P2 = MyReturnType<Func>

ConstructorParameters

interface Func1 {
  new (obj: 1, other: 'other'): [1, 'A']
}
type Func2 = new (obj: 1, other: 'other') => [1, 'A']

type MyConstructorParameters<Fn extends abstract new(...args: any) => any> =
  Fn extends new (...args: infer T) => any ? T : any

// [obj: 1, other: "other"]
type P1 = ConstructorParameters<Func1>
type P2 = MyConstructorParameters<Func1>
type P3 = ConstructorParameters<Func2>
type P4 = MyConstructorParameters<Func2>

InstanceType

interface Func1 {
  new (obj: 1, other: 'other'): [1, 'A']
}
type Func2 = new (obj: 1, other: 'other') => [1, 'A']

type MyInstanceType<Fn extends abstract new(...args: any) => any> =
  Fn extends new (...args: any) => infer T ? T : any

// [1, "A"]
type P1 = InstanceType<Func1>
type P2 = MyInstanceType<Func1>
type P3 = InstanceType<Func2>
type P4 = MyInstanceType<Func2>

ThisParameterType

interface Person {
  name: string;
  age: number;
}

type MyThisParameterType<Fn extends (this: any, ...args: any) => any> =
  Fn extends (this: infer T) => any ? T : unknown 

type Func = (this: Person) => void

// Person
type P1 = ThisParameterType<Func>
type P2 = MyThisParameterType<Func>

OmitThisParameter

interface Person {
  name: string;
  age: number;
}

type MyOmitThisParameter<Fn extends (this: any, ...args: any) => any> =
  Fn extends (this: any, ...args: infer T) => infer R ? (...args: T) => R : unknown 

type Func = (this: Person, name: Person['name'], age: 12) => Person

// (name: string, age: 12) => Person
type P1 = OmitThisParameter<Func>
type P2 = MyOmitThisParameter<Func>

Partial

interface Person {
  name: string;
  age: number;
}

type MyPartial<T extends Record<any, any>> = {
  [K in keyof T]?: T[K]
}

// { name?: string | undefined; age?: number | undefined; }
type P1 = Partial<Person>
type P2 = MyPartial<Person>

Required

interface Person {
  name: string;
  age: number;
}
type PartialPerson = Partial<Person>

type MyRequired<T extends Record<any, any>> = {
  [K in keyof T]-?: T[K]
}

// { name: string; age: number; }
type P1 = Required<PartialPerson>
type P2 = MyRequired<PartialPerson>

Readonly

interface Person {
  name: string;
  age: number;
}

type MyReadonly<T extends Record<any, any>> = {
  readonly [K in keyof T]: T[K]
}

// { name: string; age: number; }
type P1 = Readonly<Person>
type P2 = MyReadonly<Person>

Pick

interface Obj {
  a: 'A';
  b: 'B';
  1: '1';
  2: '2';
}

type MyPick<O extends Record<string, any>, KeyS extends keyof O> = {
  [Key in KeyS]: O[Key]
}

// { a: 'A'; 1: '1'; }
type a = Pick<Obj, 'a' | 1>
type b = MyPick<Obj, 'a' | 1>

Record

// string | number | symbol
type a = string | number | symbol
type b = keyof any

type MyRecord<K extends keyof any, T> = {
  [Key in K]: T
}

// { 1: void; a: void; }
type P1 = Record<1 | 'a', void>
type P2 = MyRecord<1 | 'a', void>

Exclude

type MyExclude<K extends keyof any, T> = K extends T ? never : K

// symbol | "a"
type P1 = Exclude<1 | 'a' | symbol, 1>
type P2 = MyExclude<1 | 'a' | symbol, 1>

Extract

type MyExtract<K extends keyof any, T> = K extends T ? K : never

// symbol | 1
type P1 = Extract<1 | 'a' | symbol, 1 | symbol>
type P2 = MyExtract<1 | 'a' | symbol, 1 | symbol>

Omit

interface Person {
  name: string;
  age: number;
}

type MyOmit<K extends Record<string, any>, T> = Pick<K, Exclude<keyof K, T>>

// { age: number; }
type P1 = Omit<Person, 'name'>
type P2 = MyOmit<Person, 'name'>

Awaited

type MyAwaited<T extends Promise<any>> =
  T extends Promise<infer K>
    ? K extends Promise<any> ? MyAwaited<K> : K
    : T

// [1]
type P1 = Awaited<Promise<[1]>>
type P2 = MyAwaited<Promise<[1]>>
type P3 = Awaited<Promise<Promise<[1]>>>
type P4 = MyAwaited<Promise<Promise<[1]>>>
type P5 = Awaited<Promise<Promise<Promise<[1]>>>>
type P6 = MyAwaited<Promise<Promise<Promise<[1]>>>>

NonNullable

type MyNonNullable<T> = T extends null | undefined ? never : T

// Promise<[1]>
type P1 = NonNullable<Promise<[1]>>
type P6 = MyNonNullable<Promise<[1]>>

// never
type P2 = NonNullable<null>
type P3 = NonNullable<never>
type P4 = NonNullable<undefined>
type P7 = MyNonNullable<null>
type P8 = MyNonNullable<never>
type P9 = MyNonNullable<undefined>

// void & {}
type P5 = NonNullable<void>
// void
type P10 = MyNonNullable<void>

Uppercase

Lowercase

Capitalize

Uncapitalize

sufuwang commented 1 year ago

TypeScript 内置类型 - 值得深究的官方实现

keyof any

/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

type a = keyof any  // string | number | symbol

extends

/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

/**
 * T(1) extends 1 ? never : T ==> never
 * T('a') extends 1 ? never : T ==> 'a'
 * T(symbol) extends 1 ? never : T ==> symbol
 */
type a = Exclude<1 | 'a' | symbol, 1>  // symbol | "a"

in + extends

/**
 * Construct a type with the properties of T except for those in type K.
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

type MyOmit<T, U> = {
  [key in keyof T as key extends U ? never : key]: T[key]
}
sufuwang commented 1 year ago

type & interface 的区别 - 微软面试真题

Differences Between Type Aliases and Interfaces

Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

类型别名和接口非常相似,在很多情况下您可以在它们之间自由选择。 接口的几乎所有功能都可以在类型中使用,关键区别在于类型不能重新打开以添加新属性,而接口始终是可扩展的。

  1. type 不能同名,type 使用联合类型进行扩展
  2. interface 可以同名,同名时类型自动合并,或者使用 extends 关键字进行合并
type T1 = {
  a: 'A'
}
type T2 = {
  b: 'B'
}
type T = T1 & T2

interface I {
  a: 'A'
}
interface I {
  b: 'B'
}

const t1: T = { a: 'A', b: 'B' }
const t2: I = t1
sufuwang commented 1 year ago

类型检查

协变

协变指的是子类型变量可以赋值给父类型

interface Parent {
    name: string
    age: number
}
interface Son extends Parent {
    tel: string
}

const s1: Son = {
    name: '1',
    age: 1,
    tel: '1'
}
const p1: Parent = s1  // 子类型满足父类型约束

const p2: Parent = {
    name: '1',
    age: 1,
}
// TS2741: Property 'tel' is missing in type 'Parent' but required in type 'Son'
const s2: Son = p2  // 父类型不满足子类型约束

或者这个例子更容易理解一些

type Son = string;
type Parent = keyof any;

let s: Son = "";
let p: Parent = 1;

// Type 'number' is not assignable to type 'string'.ts(2322)
s = p;
p = s;

逆变

逆变指的是父类型函数参数可以赋给子类型函数参数

interface Parent {
    name: string
    age: number
}
interface Son extends Parent {
    tel: string
}

type ParFn = (arg: Parent) => void
type SonFn = (arg: Son) => void

let p: ParFn = (p) => p.name
let s: SonFn = (s) => s.tel

s = p  // 父类型一定满足子类型约束
s = (p) => p.name
/**
 * TS2322: Type 'SonFn' is not assignable to type 'ParFn'.
 * Types of parameters 'arg' and 'arg' are incompatible.
 * Property 'tel' is missing in type 'Parent' but required in type 'Son'.
 */
p = s  // 子类型的实现可能超出父类型约束
p = (s) => s.tel

函数返回参数执行协变

interface Parent {
    name: string
    age: number
}
interface Son extends Parent {
    tel: string
}

type ParFn = () => Parent
type SonFn = () => Son

let p: ParFn = () => ({}) as Parent
let s: SonFn = () => ({}) as Son

/**
 * TS2322: Type 'ParFn' is not assignable to type 'SonFn'.
 * Property 'tel' is missing in type 'Parent' but required in type 'Son'.
 */
s = p // 返回参数子类型可以付给父类型
p = s

逆变的影响

type GetReturnType_A<Fn> = Fn extends (...args: unknown[]) => infer T ? T : never
type GetReturnType_B<Fn> = Fn extends (...args: any[]) => infer T ? T : never

// never
type G1 = GetReturnType_A<(a: 'A') => 1>
// 1
type G2 = GetReturnType_B<(a: 'A') => 1>

逆变的用途

type UnionToIntersection<U> =
  (U extends U ? (arg: U) => void : never) extends (arg: infer R) => void ? R : never

// {a: "A"} & {b: "B"}
type A = UnionToIntersection<{a:'A'} | {b:'B'}>

不变

只要不是父子类型的系统,就不可以相互赋值。在 java 中使用extends 关键字确立父子关系的,这种被称为名义类型系统,而 typescript 是结构类型系统,即结构上是一致的就可以确立父子关系

typescript 中,类型描述更具体的则是子类型,举两个例子

协变表示子类型可以赋值给父类型,父类型不可以赋值给子类型,因为子类型中存在父类型不存在的类型声明,所以 TypeA 是父类型,TypeB 是子类型

interface TypeA {
    name: string
    age: number
}
interface TypeB {
    name: string
    age: number
    tel: string
}

let a: TypeA = {} as TypeA
let b: TypeB = {} as TypeB

a = b
// TS2741: Property 'tel' is missing in type 'TypeA' but required in type 'TypeB'.
b = a

逆变表示子类型参数的函数不能赋值给父类型参数的函数,因为子类型包含一些父类型不存在类型声明,具体执行的时候可能会出错,所以 TypeA 是父类型,TypeB 是子类型。TypeA 有三种可能,TypeB 只有其中的两种,所以 TypeB 更加具体,也可以说明 TypeB 是子类型

type TypeA = 1 | 2 | 3
type TypeB = 1 | 2
type FuncA = (arg: TypeA) => void
type FuncB = (arg: TypeB) => void

let a: FuncA = () => null
let b: FuncB = () => null

b = a
/**
 * TS2322: Type 'FuncB' is not assignable to type 'FuncA'.
 * Types of parameters 'arg' and 'arg' are incompatible.
 * Type 'TypeA' is not assignable to type 'TypeB'.
 * Type '3' is not assignable to type 'TypeB'.
 */
a = b

双向协变

双向协变指的是函数参数不受逆变限制,带有父子类型参数的函数可以相互赋值,只需要一个设置 strictFunctionTypes 即可