定义一个只有一个属性的类型,该属性的名称必须在预定义的名称集中。

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

Define a type with only one property and the name of that property must be in a predefined set of names

问题

以下是翻译好的部分:

让我们假设我想创建一个错误结构,用于指示错误是在服务器端还是客户端。示例:

const serverErr = {
  server: "Error!";
};

const clientErr = {
  client: "Error!";
};

错误对象必须只有一个属性,该属性的名称必须是 serverclient

我尝试了这个问题的答案,但它不起作用。

定义一个只有一个属性的类型,该属性的名称必须在预定义的名称集中。

根据这个答案,这是 IsSingleKey 的定义:

export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void)
    ? I
    : never;
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;
type ISingleKey<K extends string, T> = IsUnion<K> extends true ? "Can only contain a single key" : Record<K, T>;

希望这对您有所帮助。

<details>
<summary>英文:</summary>

Let&#39;s say I want to create an error structure that indicates if the error is on the server side or the client side. Example:

```typescript
const serverErr = {
  server: &quot;Error!&quot;
};

const clientErr = {
  client: &quot;Error!&quot;
};

The error object must have only one property, and the name of that property must be server or client.

I tried the answer to this question question but it's not working.

定义一个只有一个属性的类型,该属性的名称必须在预定义的名称集中。

According to this answer, here is the definition of IsSingleKey:

export type UnionToIntersection&lt;U&gt; = (U extends any ? (k: U) =&gt; void : never) extends ((k: infer I) =&gt; void)
    ? I
    : never;
type IsUnion&lt;T&gt; = [T] extends [UnionToIntersection&lt;T&gt;] ? false : true;
type ISingleKey&lt;K extends string, T&gt; = IsUnion&lt;K&gt; extends true ? &quot;Can only contain a single key&quot; : Record&lt;K, T&gt;;

答案1

得分: 3

以下是翻译好的部分:

One approach is to make an error type AllowedErrors which is a union of all the allowable types, each member of which has exactly one of the acceptable keys as required, and prohibits defined values for the other keys. It would look like this:

一种方法是创建一个错误类型 AllowedErrors,它是所有可接受类型的联合类型,其中的每个成员都要求有一个可接受的键,同时禁止其他键的定义。它会看起来像这样:

type AllowedErrors = 
  { server: string; client?: never; x?: never; y?: never; z?: never; } | 
  { client: string; server?: never; x?: never; y?: never; z?: never; } | 
  { x: string; server?: never; client?: never; y?: never; z?: never; } | 
  { y: string; server?: never; client?: never; x?: never; z?: never; } | 
  { z: string; server?: never; client?: never; x?: never; y?: never; };

Note that TypeScript doesn't explicitly support prohibiting a property key, but you can make it an optional property whose value type is the impossible never type, then the only value you might find at that key would be undefined.

请注意,TypeScript 并不明确支持禁止属性键,但你可以将其设置为可选属性,其值类型为不可能的 never 类型,那么你在该键上可能找到的唯一值将是 undefined

This should behave how you want:

这应该按照你的期望来工作:

const serverErr: AllowedErrors = {
    server: "Error!"
};

const clientErr: AllowedErrors = {
    client: "Error!"
};

const bothErr: AllowedErrors = {
    server: "Error",
    client: "Error"
} // error! Type '{ server: string; client: string; }'
// is not assignable to type 'AllowedErrors'.

const neitherErr: AllowedErrors = {
    oops: "Error"
} // Type '{ oops: string; }' is not assignable
// to type 'AllowedErrors'.

const okayErr = Math.random() < 0.5 ? { x: "" } : { y: "" };

So, that's great.

因此,这很好。


But obviously you don't want to have to write or modify your AllowedErrors type manually. You want to generate it from a union of the keys like

但显然,你不想手动编写或修改你的 AllowedErrors 类型。你希望从键的联合类型中生成它,就像这样:

type AllowedErrorKeys = "server" | "client" | "x" | "y" | "z";

so that you just need to add/remove things there. Well, here's one approach:

这样,你只需要在那里添加/移除内容。好吧,下面是一种方法:

type SingleKeyValue<K extends PropertyKey, V, KK extends PropertyKey = K> =
    K extends PropertyKey ?
    Record<K, V> & { [P in Exclude<KK, K>]?: never } extends infer O ?
    { [P in keyof O]: O[P] } : never : never;

The type SingleKeyValue<K, V> produces the relevant union where each member has only one allowed key from K with value type V and the remainder of the keys from K are prohibited.

类型 SingleKeyValue<K, V> 生成相关的联合类型,其中每个成员只有来自 K 的一个允许的键,其值类型为 V,并禁止来自 K 的其余键。

First, the type is wrapped with the seemingly no-op K extends PropertyKey ? ... : never, which actually a distributive conditional type that splits K up into its individual union members, operates on each member, and then unites the results back into another union.

首先,这个类型似乎被包装成没有操作的 K extends PropertyKey ? ... : never,实际上是一个分布式条件类型,它将 K 分成其各自的联合成员,对每个成员进行操作,然后将结果合并为另一个联合类型。

The inner type is essentially Record<K, V> & { [P in Exclude<KK, K>]?: never }; the first piece using the Record<K, V> utility type to represent an object with the one key from K and the value type V, which is intersected with a type prohibiting all keys from Exclude<KK, K> using the Exclude<T, U> utility type. Wait, what's KK? That's a "dummy" type parameter I declared in the outer scope which defaults to K. This is really just a trick so that K is broken into its union pieces while KK is a copy of the original union K, so that we can express "all the members of the original union except this one".

内部类型实际上是 Record<K, V> & { [P in Exclude<KK, K>]?: never };第一个部分使用the Record<K, V> 实用类型 来表示一个

英文:

One approach is to make an error type AllowedErrors which is a union of all the allowable types, each member of which has exactly one of the acceptable keys as required, and prohibits defined values for the other keys. It would look like this:

type AllowedErrors = 
  { server: string; client?: never; x?: never; y?: never; z?: never; } | 
  { client: string; server?: never; x?: never; y?: never; z?: never; } | 
  { x: string; server?: never; client?: never; y?: never; z?: never; } | 
  { y: string; server?: never; client?: never; x?: never; z?: never; } | 
  { z: string; server?: never; client?: never; x?: never; y?: never; };

Note that TypeScript doesn't explicitly support prohibiting a property key, but you can make it an optional property whose value type is the impossible never type, then the only value you might find at that key would be undefined.

This should behave how you want:

const serverErr: AllowedErrors = {
    server: &quot;Error!&quot;
};

const clientErr: AllowedErrors = {
    client: &quot;Error!&quot;
};

const bothErr: AllowedErrors = {
    server: &quot;Error&quot;,
    client: &quot;Error&quot;
} // error! Type &#39;{ server: string; client: string; }&#39; 
// is not assignable to type &#39;AllowedErrors&#39;

const neitherErr: AllowedErrors = {
    oops: &quot;Error&quot;
} // Type &#39;{ oops: string; }&#39; is not assignable 
// to type &#39;AllowedErrors&#39;.

const okayErr = Math.random() &lt; 0.5 ? { x: &quot;&quot; } : { y: &quot;&quot; };

So, that's great.


But obviously you don't want to have to write or modify your AllowedErrors type manually. You want to generate it from a union of the keys like

type AllowedErrorKeys = &quot;server&quot; | &quot;client&quot; | &quot;x&quot; | &quot;y&quot; | &quot;z&quot;;

so that you just need to add/remove things there. Well, here's one approach:

type SingleKeyValue&lt;K extends PropertyKey, V, KK extends PropertyKey = K&gt; =
    K extends PropertyKey ?
    Record&lt;K, V&gt; &amp; { [P in Exclude&lt;KK, K&gt;]?: never } extends infer O ?
    { [P in keyof O]: O[P] } : never : never;    

The type SingleKeyValue&lt;K, V&gt; produces the relevant union where each member has only one allowed key from K with value type V and the remainder of the keys from K are prohibited.

First, the type is wrapped with the seemingly no-op K extends PropertyKey ? ... : never, which actually a distributive conditional type that splits K up into its individual union members, operates on each member, and then unites the results back into another union.

The inner type is essentially Record&lt;K, V&gt; &amp; { [P in Exclude&lt;KK, K&gt;]?: never }; the first piece using the Record&lt;K, V&gt; utility type to represent an object with the one key from K and the value type V, which is intersected with a type prohibiting all keys from Exclude&lt;KK, K&gt; using the Exclude&lt;T, U&gt; utility type. Wait, what's KK? That's a "dummy" type parameter I declared in the outer scope which defaults to K. This is really just a trick so that K is broken into its union pieces while KK is a copy of the original union K, so that we can express "all the members of the original union except htis one".

Anyway that's the type we need except it's pretty ugly (an intersection of utility types), so I use a technique described at https://stackoverflow.com/q/57683303/2887218, ... extends infer O ? { [P in keyof O]: O[P] } : never, to copy the type to another type parameter and iterate its properties with a mapped type to get a single object type.


And now we can just write

type AllowedErrors = SingleKeyValue&lt;AllowedErrorKeys, string&gt;;

and use IntelliSense to verify that it evaluates to

/* type AllowedErrors = 
  { server: string; client?: never; x?: never; y?: never; z?: never; } | 
  { client: string; server?: never; x?: never; y?: never; z?: never; } | 
  { x: string; server?: never; client?: never; y?: never; z?: never; } | 
  { y: string; server?: never; client?: never; x?: never; z?: never; } | 
  { z: string; server?: never; client?: never; x?: never; y?: never; }*/

as desired.

Playground link to code

huangapple
  • 本文由 发表于 2023年2月19日 18:22:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/75499427.html
  • javascript
  • typescript

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

确定