zl程序教程

您现在的位置是:首页 >  前端

当前栏目

[Typescript] Default value for Builder pattern - 04 (keyof {} -> never)

typescript for gt value 04 Default pattern Builder
2023-09-14 08:59:11 时间

From previous post, Builder pattern - 03

If we do the following changes:

- class TypeSafeStringMap<TMap extends Record<string, string> = {}> {
+ class TypeSafeStringMap<TMap extends Record<string, string>> {

It has a big impact of the codebase:

When we have the default value:

class TypeSafeStringMap<TMap extends Record<string, string> = {}> {}

const map = new TypeSafeStringMap()
//      ^? TypeSafeStringMap<{}>

When we don't have the default value:

class TypeSafeStringMap<TMap extends Record<string, string>> {}

const map = new TypeSafeStringMap()
//      ^? TypeSafeStringMap<Record<string, string>>

So, when we set/get the value, we will see the differences

class TypeSafeStringMap<TMap extends Record<string, string> = {}> {}

const map = new TypeSafeStringMap().set('name', 'abc')
//  map: TypeSafeStringMap<Record<"name", string>>
class TypeSafeStringMap<TMap extends Record<string, string>> {}

const map = new TypeSafeStringMap().set("name", "abc")
//      ^? TypeSafeStringMap<Record<string, string> & Record<"name", string>>

Take a close look of difference:

// with default {} 
TypeSafeStringMap<Record<"name", string>>

// without default {}
TypeSafeStringMap<Record<string, string> & Record<"name", string>>

When we have getter function:

  get(key: keyof TMap): string {
    return this.map[key];
  }

// without default value
type a = keyof Record<"name", string>
//    ^? "name"
type b = keyof (Record<string, string> & Record<"name", string>)
//    ^? string

b is type string, because string | "name" results in string type.

Why a work fine, this is because

type c = keyof {}
//    ^? never
type d = never | "name"
//    ^? "name"

Tips:

type isNever<T> = [T] extends [keyof {}] ? true: false;

type e = isNever<never> // true
type f = isNever<false> // false