在对象 – 类型交集与迭代方法中添加额外成员

huangapple go评论70阅读模式
英文:

Append an extra member to an object - type intersection vs iteration approach

问题

以下是翻译好的内容:

这是一个问题:AppendToObject。我对这个问题的第一直觉是使用类型交集,例如,

type AppendToObject<T, U extends PropertyKey, V> = T & {[P in U]: V}

然而,这个解决方案不能通过测试用例。

type test1 = {
  key: 'cat'
  value: 'green'
}

type testExpect1 = {
  key: 'cat'
  value: 'green'
  home: boolean
}

type test2 = {
  key: 'dog' | undefined
  value: 'white'
  sun: true
}

type testExpect2 = {
  key: 'dog' | undefined
  value: 'white'
  sun: true
  home: 1
}

type test3 = {
  key: 'cow'
  value: 'yellow'
  sun: false
}

type testExpect3 = {
  key: 'cow'
  value: 'yellow'
  sun: false
  moon: false | undefined
}

// 引用自
// https://github.com/type-challenges/type-challenges/blob/main/utils/index.d.ts
type Equal<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false
type Expect<T extends true> = T

type cases = [
  Expect<Equal<AppendToObject<test1, 'home', boolean>, testExpect1>>,
  Expect<Equal<AppendToObject<test2, 'home', 1>, testExpect2>>,
  Expect<Equal<AppendToObject<test3, 'moon', false | undefined>, testExpect3>>,
]

所以我改成了迭代的方法,它通过了所有的测试用例。

type AppendToObject<T, U extends PropertyKey, V> = {[P in U | keyof T ]: P extends keyof T ? T[P] : V}

我没有看到交集方法有什么问题,因为如果我使用它来定义一个对象,也必须指定额外的参数。解决方案是否因为 Equal 的实现方式而无法通过测试?或者,这两种方法之间是否存在我不知道的其他细微差别?

英文:

Here is the question: AppendToObject. My first intuition to the question was to use type intersection, e.g,

type AppendToObject&lt;T, U extends PropertyKey, V&gt; = T &amp; {[P in U]: V}

However, that solution doesn't pass the testcases.

type test1 = {
  key: &#39;cat&#39;
  value: &#39;green&#39;
}

type testExpect1 = {
  key: &#39;cat&#39;
  value: &#39;green&#39;
  home: boolean
}

type test2 = {
  key: &#39;dog&#39; | undefined
  value: &#39;white&#39;
  sun: true
}

type testExpect2 = {
  key: &#39;dog&#39; | undefined
  value: &#39;white&#39;
  sun: true
  home: 1
}

type test3 = {
  key: &#39;cow&#39;
  value: &#39;yellow&#39;
  sun: false
}

type testExpect3 = {
  key: &#39;cow&#39;
  value: &#39;yellow&#39;
  sun: false
  moon: false | undefined
}

// reference from 
// https://github.com/type-challenges/type-challenges/blob/main/utils/index.d.ts
type Equal&lt;X, Y&gt; =
  (&lt;T&gt;() =&gt; T extends X ? 1 : 2) extends
  (&lt;T&gt;() =&gt; T extends Y ? 1 : 2) ? true : false
type Expect&lt;T extends true&gt; = T

type cases = [
  Expect&lt;Equal&lt;AppendToObject&lt;test1, &#39;home&#39;, boolean&gt;, testExpect1&gt;&gt;,
  Expect&lt;Equal&lt;AppendToObject&lt;test2, &#39;home&#39;, 1&gt;, testExpect2&gt;&gt;,
  Expect&lt;Equal&lt;AppendToObject&lt;test3, &#39;moon&#39;, false | undefined&gt;, testExpect3&gt;&gt;,
]

So I changed to the iteraction approach and it passes all the testcases.

type AppendToObject&lt;T, U extends PropertyKey, V&gt; = {[P in U | keyof T ]: P extends keyof T ? T[P] : V}

I'm not seeing anything wrong with the intersection approach, as if I type an object with it, it's also compulsory to specify the extra argument. Does the solution fail the tests just because of how Equal is implemented? Or, is there any other nuances between these two approaches that I'm not aware?

答案1

得分: 1

AppendToObject看起来结果类型是正确的。所以我怀疑问题在于Equal如何比较这些类型。示例:

type test1 = {
  key: 'cat';
  value: 'green';
};
type testExpect1 = {
  key: 'cat';
  value: 'green';
  home: boolean;
};

// type Result = test1 & {
//     home: boolean;
// }
type Result = AppendToObject<test1, 'home', boolean>

所以testExpect1没有交集,但属性直接在一个对象中。

这可以通过使用type-samurai包中的Prettify实用类型来修复。简化版本:

type Prettify<T> = T extends infer R
  ? {
      [K in keyof R]: R[K];
    }
  : never;

Prettify重新映射类型,导致移除交集并使其更易读。

测试:

type AppendToObject<T, U extends PropertyKey, V> = Prettify<
  T & { [P in U]: V }
>;

// type Result = {
//   key: 'cat';
//   value: 'green';
//   home: boolean;
// }
type Result = AppendToObject<test1, 'home', boolean>;

播放场地

英文:

The result types for the AppendToObject looks correct. So I suspect the issue is with how the Equal compares the types. Example:

type test1 = {
key: &#39;cat&#39;;
value: &#39;green&#39;;
};
type testExpect1 = {
key: &#39;cat&#39;;
value: &#39;green&#39;;
home: boolean;
};
// type Result = test1 &amp; {
//     home: boolean;
// }
type Result = AppendToObject&lt;test1, &#39;home&#39;, boolean&gt;

So the testExpect1 has no intersection, but the property is directly in one object.

This can be fixed by using the Prettify utility type from the type-samurai package. Simplified version:

type Prettify&lt;T&gt; = T extends infer R
? {
[K in keyof R]: R[K];
}
: never;

Prettify remaps the type which causes to remove the intersection and make the more readable.

Testing:

type AppendToObject&lt;T, U extends PropertyKey, V&gt; = Prettify&lt;
T &amp; { [P in U]: V }
&gt;;
// type Result = {
//   key: &#39;cat&#39;;
//   value: &#39;green&#39;;
//   home: boolean;
// }
type Result = AppendToObject&lt;test1, &#39;home&#39;, boolean&gt;;

playground

huangapple
  • 本文由 发表于 2023年7月17日 19:16:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76703910.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定