semlinker/awesome-typescript

「重学TS 2.0 」TS 练习题第三十题

Opened this issue · 9 comments

完善 Chainable 类型的定义,使得 TS 能成功推断出 result 变量的类型。调用 option 方法之后会不断扩展当前对象的类型,使得调用 get 方法后能获取正确的类型。

declare const config: Chainable

type Chainable = {
  option(key: string, value: any): any
  get(): any
}

const result = config
  .option('age', 7)
  .option('name', 'lolo')
  .option('address', { value: 'XiaMen' })
  .get()

type ResultType = typeof result  
// 期望 ResultType 的类型是:
// {
//   age: number
//   name: string
//   address: {
//     value: string
//   }
// }

请在下面评论你的答案。

declare const config: Chainable

type Simplify<T> = {
    [P in keyof T]: T[P]
}

type Chainable<T = {}> = {
   // S extends string can make S is Template Literal Types
    option<V, S extends string>(key: S, value: V): Chainable<T & {
       // use Key Remapping in Mapped Types generate {  S: V } type  https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html#key-remapping-in-mapped-types
        [P in keyof {
            S: S,
        } as `${S}`]: V
    }>
    get(): Simplify<T>
}

const result = config
    .option('age', 7)
    .option('name', 'lolo')
    .option('address', { value: 'XiaMen' })
    .get()


type ResultType = typeof result

个人觉得这道题主要是要发现, config 可以进行链式调用, 这样可以很容易的联想到 js 中的 return this 这种思路, 那么这里 option 的返回值就应该是一个新的 Chainable, 把添加了新属性的类型当做下一个 ChainableT 即可

关于如何动态命名 key, 可以看一下官方介绍

//  给基础类型 T 增加 { K, V } 键值对
type ReudceType<K extends string, V extends any, T = {}> = T & { [key in [K] as `${K}`]: V }

type Chainable<T = {}> = {
  option<K extends string, V extends any>(key: K, value: V): Chainable<ReudceType<K, V, T>> 
  get(): { [K in keyof T]: T[K] }    //  这里也可以直接返回 T, 不过这样并不直观, 所以手动遍历一下
}

//  测试用例
const result = config
  .option('age', 7)
  .option('name', 'lolo')
  .option('address', { value: 'XiaMen' })
  .get()

type ResultType = typeof result  
ln0y commented
type Chainable<R = {}> = {
  option<K extends string | number | symbol, V> (key: K, value: V): Chainable<R & Record<K, V>>
  get (): R
}
declare const config: Chainable

type ITypes = string | number | symbol;
type Chainable<T = {}> = {
  option<K extends ITypes, V extends any>(key: K, value: V): Chainable<T & Record<K, V>>;
  get(): T;
}

const result = config
  .option('age', 7)
  .option('name', 'lolo')
  .option('address', { value: 'XiaMen' })
  .get()

type ResultType = typeof result
const t: ResultType = {
  age: 30,
  name: 'hello',
  address: {
    value: 'Huizhou'
  }
}
console.log(t);
// 期望 ResultType 的类型是:
// {
//   age: number
//   name: string
//   address: {
//     value: string
//   }
// }

思路: 链式操作的思维

declare const config: Chainable<{}>

type Chainable<T extends {}> = {
  option<K extends PropertyKey, V>(key: K, value: V): Chainable<T & {
    [p in K]: V
  }>
  get(): {
    [p in keyof T]: T[p]
  }
}

const result = config
  .option('age', 7)
  .option('name', 'lolo')
  .option('address', { value: 'XiaMen' })
  .option(3, true)
  .get()

type ResultType = typeof result  
declare const config: Chainable;

type Chainable<T0 = {}> = {
  option<T, U>(key: keyof T, value: U): Chainable<T0 & { [P in keyof T]: U }>;
  get(): T0;
};

const result = config.option("age", 7).option("name", "lolo").option("address", { value: "XiaMen" }).get();

type ResultType = typeof result;
// 期望 ResultType 的类型是:
// {
//   age: number
//   name: string
//   address: {
//     value: string
//   }
// }
declare const config: Chainable

type Chainable<T = {}> = {
    option<K extends keyof any, V>(key: K, value: V): Chainable<T & {[P in K]: V}>;
    get(): T;
}

const result = config
    .option('age', 7)
    .option('name', 'lolo')
    .option('address', { value: 'XiaMen' })
    .get()

type ResultType = typeof result

已经有一样的了🤣

declare const config: Chainable;

type Chainable<T = {}> = {
  option<K extends string, V extends any>(key: K, value: V): Chainable<{ [P in K]: V } & T>;
  get(): T;
};

const result = config.option("age", 7).option("name", "lolo").option("address", { value: "XiaMen" }).get();

type ResultType = typeof result;
type Chainable<T = {}> = {
  option<K extends PropertyKey, V = unknown>(
    key: K,
    value: V
  ): Chainable<
    {
      [X in keyof T | K]: X extends K ? V : X extends keyof T ? T[X] : never;
    }
  >;
  get(): T;
};