【问题标题】:How can I create an abstraction of the map function working on single values?如何创建处理单个值的 map 函数的抽象?
【发布时间】:2021-08-17 18:43:36
【问题描述】:

我尝试创建一个类来抽象数组的map 函数以透明地处理单个值。

export class Container<T> {
  value: T;

  constructor(value: T) {
    this.value = value;
  }

  map<U>(f: (x: T extends Array<infer R> ? R : T) => U): Container<T extends Array<any> ? Array<U> : U> {
    if (Array.isArray(this.value)) {
      const mapped = this.value.map(f);
      return new Container(mapped); // <-- Error A here
    }

    return new Container<U>(f(this.value /* <-- Error B here */)); // <-- Error C here
  }

  unwrap(): T {
    return this.value;
  }
}

//Sample usage
const sayHello = (name: string) => `Hello ${name}`;
const shout = (str: string) => str.toUpperCase();

const a = new Container("Toto").map(sayHello).map(shout).unwrap() // HELLO TOTO
const b = new Container(["Harry", "Ron", "Hermione"]).map(sayHello).map(shout).unwrap() // ["HELLO HARRY", "HELLO RON", "HELLO HERMIONE"]

但是打字稿报错,我不明白为什么类型不匹配

// Error A 
Type 'Container<U[]>' is not assignable to type 'Container<T extends any[] ? U[] : U>'.
  Type 'U[]' is not assignable to type 'T extends any[] ? U[] : U'.ts(2322)

// Error B 
Argument of type 'T' is not assignable to parameter of type 'T extends (infer R)[] ? R : T'

// Error C
Type 'Container<U>' is not assignable to type 'Container<T extends any[] ? U[] : U>'.
  Type 'U' is not assignable to type 'T extends any[] ? U[] : U'

我现在可以用any“修复”它,但要寻找更清洁的解决方案

【问题讨论】:

  • new Container(...) 真的比[...] 干净吗?为什么不把东西包装在一个数组而不是一个自定义类中?
  • 你找到this question了吗?我想这是同一个问题,但我不是 100% 确定
  • @A_A 谢谢,这解释了为什么我的解决方案不起作用!

标签: typescript


【解决方案1】:

这是一个有点笨拙但有效的解决方案:让 T 成为单个元素类型(而不是 either 元素类型或数组类型),然后为value 是否是一个数组。您仍然需要在 unwrap 方法中进行类型断言。

我还不得不将构造函数替换为带有两个重载的静态工厂方法,这样才能正确提供第二个类型参数。

export class Container<T, IsArray extends boolean> {
  value: T | T[];
  
  static of<T>(value: T[]): Container<T, true>;
  static of<T>(value: T): Container<T, false>;
  static of(value: unknown) {
    return new Container(value);
  }
  private constructor(value: T | T[]) {
    this.value = value;
  }

  map<U>(f: (x: T) => U): Container<U, IsArray> {
    if (Array.isArray(this.value)) {
      const mapped = this.value.map(f);
      return new Container(mapped);
    }

    return new Container<U, IsArray>(f(this.value));
  }

  unwrap(): IsArray extends true ? T[] : T {
    return this.value as IsArray extends true ? T[] : T;
  }
}

Playground Link

也就是说,如果您只使用普通数组,它会简单得多。您可以将单个值包装在像[value] 这样的数组中,而不是Container.of(value),并且没有比将其包装在Container 实例中更多的开销;然后当您的值是一个数组时不需要任何开销。比较:

const a = ["Toto"].map(sayHello).map(shout)[0];
const b = ["Harry", "Ron", "Hermione"].map(sayHello).map(shout);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-12
    • 2019-08-27
    • 1970-01-01
    • 1970-01-01
    • 2019-08-17
    • 2014-11-26
    • 1970-01-01
    相关资源
    最近更新 更多