【问题标题】:How to set a Type Literal on a widened type parameter (or prevent type widening)?如何在扩大的类型参数上设置类型文字(或防止类型扩大)?
【发布时间】:2019-12-21 08:08:18
【问题描述】:

我想创建一个生成强类型对象“工厂”的函数。这些对象有一个名为“tag”的属性,即string,但我想将此字符串设为string literal

这是实现某种“AbstractFactory”模式所必需的,换句话说,是一组创建所需对象的对象的函数,如下所示:

//Over Simplified Version

type Factory = {
  a: (name: string) => { tag: name };
  b: (name: string) => { tag: name };
};

type Names = {
  a: "A";
  b: "B";
};

type Result = {
  a: { tag: "A" };
  b: { tag: "B" };
};

function(factory): (names) => result

问题是这个“标签”对象有额外的属性,我需要这个字符串字面量才能正确使用它们。

我对这个概念做了各种实验,得到的是这样的:

// Implementation

type Tag<T extends string> = { tag: T };

type Factory<
  TKey extends string,
  TName extends string,
  TTag extends Tag<TName>
> = {
  [key in TKey]: (name: TName) => TTag;
};

type Names<TFactory extends Factory<any, any, any>, TName extends string> = {
  [key in keyof TFactory]: TName;
};

type Result<
  TFactory extends Factory<any, any, any>,
  TNames extends Names<TFactory, any>
> = {
  /**
   * I am almost sure that the problem lies here, maybe because I am
   * not passing the required generic, I just can't figure it out.
   */
  [key in keyof TNames]: ReturnType<TFactory[key]>;
};

function implementation<
  TKey extends string,
  TName extends string,
  TTag extends Tag<TName>,
  TFactory extends Factory<TKey, TName, TTag>,
  TNames extends Names<TFactory, TName>,
  TResult extends Result<TFactory, TNames>
>(factory: TFactory): (names: TNames) => TResult {
  return (names) => {
    const keys = Object.keys(factory) as Extract<keyof TFactory, string>[];
    return keys.reduce((result, key) => {
      const name: TName = names[key];
      (result as Record<string, TTag>)[key] = factory[key](name);
      return result;
    }, {}) as TResult;
  };
}

const factory = implementation({
  a: (name: string) => ({ tag: name, index: 2 }),
  b: (name: string) => ({ tag: name, name: "Bob" }),
});

const result = factory({ a: "A", b: "B" } as const/* names */);

result.a.index; // the index type got inferred properly
result.b.name; // the name type got inferred properly

result.a.tag; // the tag type got widened to string, I want to be string literal "A"
result.b.tag; // the tag type got widened to string, I want to be string literal "B"

我认为我有点过度使用泛型。我很确定有一种更简单的方法。

TL;DR:我想防止将字符串文字扩大到 string,因此我的 tag 属性成为我提供的文字。

【问题讨论】:

    标签: typescript types type-inference


    【解决方案1】:

    可能有比这更理想的解决方案,因为我通常更喜欢在 TypeScript 中避免类型相交 (&amp;)。并不是说它们一定是不好的做法,但与使用其他方法时相比,它们往往会在编辑器的工具提示中产生可读性较差的类型提示。

    interface Tag<T extends string> {
      tag: T;
    }
    
    interface Factory {
      (name: string): Tag<string>;
    }
    
    interface FactoryMapper {
      [key: string]: Factory;
    }
    
    type Names<TMapper extends FactoryMapper> = {
      [TKey in keyof TMapper]: string;
    }
    
    type Result<TMapper extends FactoryMapper, TNames extends Names<TMapper>> = {
      [TKey in keyof TMapper]: ReturnType<TMapper[TKey]> & Tag<TNames[TKey]>;
    }
    
    function implementation<TMapper extends FactoryMapper> (mapper: TMapper) {
      return <TNames extends Names<TMapper>> (names: TNames) => (
        Object.keys(mapper).reduce<Partial<Result<TMapper, TNames>>>(
          (result, key) => Object.assign(
            result,
            { [key]: mapper[key](names[key]) }
          ),
          {}
        ) as Result<TMapper, TNames>
      );
    }
    
    const factory = implementation({
      a: name => ({ tag: name, index: 2 }),
      b: name => ({ tag: name, name: "Bob" }),
    });
    
    const result = factory({ a: "A", b: "B" } as const);
    
    result.a.index; // the index type got inferred as number
    result.b.name; // the name type got inferred as string
    
    result.a.tag; // the tag type got inferred as "A"
    result.b.tag; // the tag type got inferred as "B"
    

    这个解决方案的关键部分是Result类型的定义:

    type Result<TMapper extends FactoryMapper, TNames extends Names<TMapper>> = {
      [TKey in keyof TMapper]: ReturnType<TMapper[TKey]> & Tag<TNames[TKey]>;
    }
    

    使用相交来缩小结果成员的定义。上面的其余代码也应该有助于举例说明如何更好地为泛型编写类型参数。

    【讨论】:

    • 感激不尽。我快到了,但你简化了一切。非常感谢。
    猜你喜欢
    • 1970-01-01
    • 2022-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多