Skip to content

20. Chainable #28

@astak16

Description

@astak16

题目

题目链接:ChainableOption回答链接

实现 Chainable ,满足下面要求

import type { Alike, Expect } from "@type-challenges/utils";

declare const a: Chainable;

const result1 = a
  .option("foo", 123)
  .option("bar", { value: "Hello World" })
  .option("name", "type-challenges")
  .get();

const result2 = a
  .option("name", "another name")
  // @ts-expect-error
  .option("name", "last name")
  .get();

type cases = [
  Expect<Alike<typeof result1, Expected1>>,
  Expect<Alike<typeof result2, Expected2>>
];

type Expected1 = {
  foo: number;
  bar: {
    value: string;
  };
  name: string;
};

type Expected2 = {
  name: string;
};

答案

方法一

type Chainable<T = unknown> = {
  option<K extends string, V>(
    key: K extends keyof T ? never : K,
    value: V
  ): Chainable<T & { [k in K]: V }>;
  get(): T;
};

知识点

用一个例子来讲解

declare const a: Chainable;
const result1 = a
  .option("foo", 123)
  .option("bar", { value: "Hello World" })
  .get();

type Result1 = {
  foo: number;
};
  1. 第一个 option 函数内部推导

    1. T = unknown, keyof T = never, K = "foo", V = 123
    2. key = "foo" extends never => false => "foo", value = 123
    3. T & {[k in K]: V} = unknown & {foo: 123} = {foo: 123}
       Chainable<{foo: 123}>
       T = {foo: 123}
  2. 第二个 option 函数内部推导

    // T 是第一个 option 函数的结果
    1. T = {foo: 123}, keyof T = "foo", K = "bar", V = { value: "Hello World" }
    2. key = "bar" extends "foo" => false => "bar", value = { value: "Hello World" }
    3. T & {[k in K]: V} = {foo: 123} & {bar: { value: "Hello World" }}
       Chainable<{foo: 123} & {bar: { value: "Hello World" }}>
       T = {foo: 123} & {bar: { value: "Hello World" }}
  3. 调用 get 得到结果,返回 T

如果调用 option 传入相同的 key 时,应该要报错,看下面的例子

const result2 = a
  .option("name", "another name")
  .option("name", "last name")
  .get();

type Result2 = {
  name: string;
};
  1. 第一个 option 推导出的结果是 {name: "another name"}

  2. 第二个 optionkey 得到的值为 never

    K extends keyof T ? never : K => "name" extends "name" => true => K = never
    Chainable<{name: "another name"}>
    T = {name: "another name"}
  3. 调用 get 得到结果,返回 T

方法二

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

知识点

用例子来讲解一步步的推算过程

declare const a: Chainable;
const result1 = a
  .option("foo", 123)
  .option("bar", { value: "Hello World" })
  .get();

type Result1 = {
  foo: number;
};
  1. 第一个 option 函数内部推导

    1. T = unknown, keyof T = never, K = "foo", V = 123
    2. key = "foo" extends never => false => "foo", value = 123
    3. 对象左边 = K | keyof T => "foo" | never => P = "foo"
       对象右边 = "foo" extends never => false => 123
       Chainable<{foo: 123}>
       T = {foo: 123}
  2. 第二个 option 函数内部推导

    // T 是第一个 option 函数的结果
    1. T = {foo: 123}, keyof T = "foo", K = "bar", V = { value: "Hello World" }
    2. key = "bar" extends "foo" => false => "bar", value = { value: "Hello World" }
    3. 对象左边 = K | keyof T => "bar" | "foo" => P = "bar" 或者 "foo"
       对象右边 =
        P = "bar" 时,"bar" extends "foo" =? false => { value: "Hello World" }
        P = "foo" 时,"foo" extends "foo" =? true => 123
       Chainable<{bar: { value: "Hello World" }; foo: 123}>
       T = {bar: { value: "Hello World" }; foo: 123}
  3. 调用 get 得到结果,返回 T

如果调用 option 传入相同的 key 时,应该要报错,看下面的例子

const result2 = a
  .option("name", "another name")
  .option("name", "last name")
  .get();

type Result2 = {
  name: string;
};
  1. 第一个 option 推导出的结果是 {name: "another name"}

  2. 第二个 optionkey 得到的值为 never

    K extends keyof T ? never : K => "name" extends "name" => true => K = never
    Chainable<{name: "another name"}>
    T = {name: "another name"}
  3. 调用 get 得到结果,返回 T

方法三

type Chainable = {
  option<T, K extends string, V>(
    this: T,
    key: K extends keyof T ? never : K,
    value: V
  ): T & { [key in K]: V };
  get<T>(this: T): Omit<T, "option" | "get">;
};

知识点

这里的 this 指代 Chainable 本身,由于它本身包含了 optionget 两个方法,所以要用 Omit 把这两个方法去除掉。

TIP

  1. { [k in K]: V } 可以用 Record<K, V> 代替
  2. K extends keyof T ? never : K 可以用 Exclude<K, keyof T> 代替

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions