英文:
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!";
};
错误对象必须只有一个属性,该属性的名称必须是 server
或 client
。
我尝试了这个问题的答案,但它不起作用。
根据这个答案,这是 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'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: "Error!"
};
const clientErr = {
client: "Error!"
};
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<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>;
答案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: "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
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.
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<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 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<AllowedErrorKeys, string>;
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论