“永远不会扩展”有什么用?

dip*_*pea 16 typescript

这是 DeepReadonly 的一个实现(取自这里,这是这个挑战的解决方案)。

type DeepReadonly<T> = keyof T extends never
  ? T
  : { readonly [k in keyof T]: DeepReadonly<T[k]> };
Run Code Online (Sandbox Code Playgroud)

这里的目的是什么extends never?我认为它是递归类型的某种基本情况,但我不明白我们什么时候会遇到它,以及为什么它首先是必要的(因为打字稿显然确实允许一些其他类型的自引用类型定义,而无需这种基本情况)例)。

jca*_*alz 23

我想我和你有同样的问题。简短的回答是,我认为这不是一个很好的实现DeepReadonly<T>,对于编写它的人来说并没有冒犯的意思。


Sokeyof T extends never意味着该类型没有已知的键T,因为keyof运算符生成已知键的并集,并且never类型是 TypeScript 的底层类型,这是一种根本没有值的类型。这意味着keyof T extends never行为如下:

type Hmm<T> = keyof T extends never ? true : false
type X1 = Hmm<{ a: string }> // false, "a" is a known key
type X2 = Hmm<{}> // true, there are no known keys
type X3 = Hmm<object> // true, there are no known keys
type X4 = Hmm<string> // false, there are keys like "toUpperCase"
type X5 = Hmm<
  { a: string } | { b: string }
> // true, unions with no common keys have no known keys
Run Code Online (Sandbox Code Playgroud)

现在,这并不是一个很好的实现方法DeepReadonly<T>,假设您只是想在遇到原始类型时停止递归。但考虑到上面的输出,事实并非如此keyof T extends never。例如:

type DeepReadonly<T> = keyof T extends never
    ? T
    : { readonly [K in keyof T]: DeepReadonly<T[K]> };

type Z = DeepReadonly<{ a: string } | { b: string }> 
// type Z = {a: string} | {b: string}  OOPS

declare const z: Z;
if ("a" in z) {
    z.a = "" // no error, not readonly
}
Run Code Online (Sandbox Code Playgroud)

由于我们传递了一个联合,编译器将其键视为never,突然间我们什么地方都没有了readonly。哎呀。


“正确”的定义DeepReadonly<T>可能就是

type DeepReadonly<T> = 
  { readonly [K in keyof T]: DeepReadonly<T[K]> };
Run Code Online (Sandbox Code Playgroud)

映射类型已经通过返回输入“跳过”基元,并且它们自动分布在联合上,因此您不需要自己检查这些:

type Look<T> = { [K in keyof T]: 123 };
type Y1 = Look<{ a: string }> // {a: 123}
type Y2 = Look<string> // string
type Y3 = Look<{ a: string } | { b: string }>
//  Look<{ a: string; }> | Look<{ b: string; }>
Run Code Online (Sandbox Code Playgroud)

因此,通过这个版本,DeepReadonly我们也获得了工会的正确行为:

type Z = DeepReadonly<{ a: string } | { b: string }> 
// type Z = DeepReadonly<{  a: string; }> | DeepReadonly<{ b: string; }>

declare const z: Z;
if ("a" in z) {
    z.a = "" // error! Cannot assign to 'a' because it is a read-only property
}
Run Code Online (Sandbox Code Playgroud)

如果您确实检查对象与基元,那么最好使用类型object

type DeepReadonly<T> = T extends object ?
    { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
Run Code Online (Sandbox Code Playgroud)

这与没有检查的类型类似:T extends object ? ... : T是一种分布式条件类型,因此它会自动拆分联合,处理它们,然后将它们重新组合在一起:

type Z = DeepReadonly<{ a: string } | { b: string }>
// type Z = {  readonly a: string; } | { readonly b: string; }
Run Code Online (Sandbox Code Playgroud)

尽管 IntelliSense 的快速信息以不同的方式显示它们,但这与之前的类型相同。


Playground 代码链接

  • 我猜他们是想跳过功能?那么我会把它写成`type DeepReadonly&lt;T&gt; = T extends Function ?T : { readonly [K in keyof T]: DeepReadonly&lt;T[K]&gt; };` 代替。当然,用例驱动定义;甚至像“Readonly&lt;T&gt;”这样的内置实用程序类型也会对​​函数做一些奇怪的事情(例如,“Readonly&lt;() =&gt; 22&gt;”会生成“{}”而不是“() =&gt; 22”),所以现在我想说,在没有特定用例要求的情况下,“DeepReadonly”的“正确”定义可能仍然是我发布的定义。 (3认同)