【问题标题】:Force generic type to be the type of a unique symbol in typescript强制泛型类型成为打字稿中唯一符号的类型
【发布时间】:2021-11-13 13:39:52
【问题描述】:

在阅读了 this answer 关于名义打字的内容后,我想将其更进一步

export type MARKED<C, M> = C & {
  readonly m: M;
};

并且可以像这样使用

const readonly SPECIAL: unique symbol = Symbol();
isSpecial(value: C): value is MARKED<C, typeof SPECIAL> {
  return someVerificationOf(value);
}

在这里,我尝试将“品牌”替换为唯一的符号来更加安全,尽管MARKED 也可以仅与字符串一起使用

class Foo { ... };
type SpecialFoo = MARKED<Foo, 'special'>;

是否有机会将MARKED 的第二个通用参数强制为unique symbol?我试过像

type MARKED<C, M extends unique symbol> = ...

但这显然是错误的。

【问题讨论】:

    标签: typescript types typescript-generics


    【解决方案1】:

    请看docs

    为了将符号视为唯一文字,可以使用新类型的唯一符号。唯一符号是符号的子类型,仅通过调用 Symbol() 或 Symbol.for() 或显式类型注释产生。新类型仅允许在 const 声明和只读静态属性 and in order to reference a specific unique symbol, you’ll have to use the typeof operator 上使用。每个对唯一符号的引用都意味着与给定声明相关联的完全唯一身份。

    特别是这个:in order to reference a specific unique symbol, you’ll have to use the typeof operator

    因此,要使用unique symbol 约束,您需要使用typeof

    
    const UNIQUE: unique symbol = Symbol();
    
    type MARKED<U extends typeof UNIQUE> = U
    
    type Result = MARKED<typeof UNIQUE>
    

    但是,我敢打赌,您有多个 unique symbols,并且您不想为每个符号创建实用程序类型。

    让我们仔细看看这条线:unique symbol is a subtype of symbol

    它给了我们什么?

    unique symbol 是符号的子类型。它为我们提供了检查某种类型是否是符号子类型的门。已经是东西了。

    所以,我们需要检查传递的类型是否是symbol的子类型:

    type IsUnique<UniqueSymbol extends symbol> =
        (UniqueSymbol extends symbol
            ? (symbol extends UniqueSymbol
                ? false
                : true
            )
            : false
        )
    
    // true
    type Check = IsUnique<symbol & { tag: 42 }>
    

    上面的 util 不符合我们的要求。

    我们还需要检查符号是否只包含symbol 键。

    type IsExactKeys<S extends symbol> =
        (keyof S extends SYMBOL_KEYS
            ? (SYMBOL_KEYS extends keyof S
                ? true
                : false
            )
            : false
        )
    
    type IsUnique<UniqueSymbol extends symbol> =
        (UniqueSymbol extends symbol
            ? (symbol extends UniqueSymbol
                ? false
                : IsExactKeys<UniqueSymbol>
            )
            : false
        )
    // never
    type Check = IsUnique<never>
    

    糟糕,它仍然无法与never 一起使用。

    让我们处理never类型:

    
    const UNIQUE: unique symbol = Symbol();
    const REGULAR: symbol = Symbol()
    
    type SYMBOL_KEYS = keyof symbol;
    
    type IsExactKeys<S extends symbol> =
        (keyof S extends SYMBOL_KEYS
            ? (SYMBOL_KEYS extends keyof S
                ? true
                : false
            )
            : false
        )
    
    // credits goes to https://github.com/microsoft/TypeScript/issues/40248
    type IsNever<T> = [T] extends [never] ? true : false;
    
    type IsUnique<UniqueSymbol extends symbol> =
        (IsNever<UniqueSymbol> extends true ? false
            : (UniqueSymbol extends symbol
                ? (symbol extends UniqueSymbol
                    ? false
                    : IsExactKeys<UniqueSymbol>
                )
                : false
            )
        );
    
    type Assert<T extends true> = T
    
    type Unique = {
        readonly StaticSymbol: unique symbol
    }['StaticSymbol']
    
    /**
     * Ok
     */
    type Test0_0 = Assert<IsUnique<Unique>> // ok
    type Test0 = Assert<IsUnique<typeof UNIQUE>> // ok
    
    /**
     * Expected errors
     */
    type Test1 = Assert<IsUnique<typeof REGULAR>> // error
    type Test2 = Assert<IsUnique<
        & typeof REGULAR
        & { readonly tag: unique symbol }
    >> // error
    
    type Test3 = Assert<IsUnique<symbol>> // error
    type Test4 = Assert<IsUnique<any>> // error
    type Test5 = Assert<IsUnique<unknown>> // error
    type Test6 = Assert<IsUnique<never>> // error
    
    
    export type MARKED<C, M extends symbol> =
        IsUnique<M> extends true ? C & {
            readonly m: M;
        } : never
    

    Playground

    现在好像可以了。请记住,上述约束是间接的。我会说这是一个duck 约束。

    如果有:

    • 符号的子类型
    • 具有与符号相同的键
    • 不是永远
    • 符号不可分配给传递的类型,但传递的类型可分配给符号

    那么,我们可能与unique symbol 达成协议。

    请添加更多测试。

    请仅在 90% 的情况下信任上述代码。

    【讨论】:

      【解决方案2】:

      这不是对主题中提出的问题的直接回答,而是针对在 typescript 中获得 nominal typing 的目标问题,而不会出现与“brand”类型名义可能发生的复杂名称冲突的危险按照this answer 中的说明输入。

      假设我们有

      class Foo { ... }
      

      然而在某些情况下,特别是在验证了Foo 对象的某些约束之后,我们想要携带约束所拥有的信息。我们使用以下内容:

      const VERIFIED_FOO = Symbol();
      type VerifiedFoo = Foo & {
        readonly [VERIFIED_FOO]: undefined;
      }
      isFoo(foo: Foo) foo is VerifiedFoo {
        return elaborateFooVerificationPassed(foo);
      }
      

      通过使用Symbol 作为附加标记类型中的属性名称,我们应该可以避免虚假名称冲突。此外,VerifiedFoo 可以在任何可以使用Foo 的地方使用,但反之则不行。最后,在使用类型保护isFoo 之后,编译器知道它并有助于不混淆这两种类型。这可以使用,例如:

      handle(foo: Foo): void {
        if (isFoo(foo)) {
          // Here we know it is a VerifiedFoo, not just Foo
          // and in particular the compiler knows it too.
          doHandle(foo);
        }
      }
      private doHandle(foo: VerifiedFoo): void {
        // no need to handle special cases for non verified foo objects
      }
      

      【讨论】:

        猜你喜欢
        • 2021-11-26
        • 1970-01-01
        • 2017-05-16
        • 1970-01-01
        • 2020-05-09
        • 2022-11-23
        • 2020-06-29
        • 1970-01-01
        • 2018-04-27
        相关资源
        最近更新 更多