I have a type Identifiers which hold different props and will be used as React component property.
type Identifiers = {
alias: string;
personalId: number;
customerId: number;
};
I want to create a type which will allow to specify only one of identifiers and omit others (not make others optional).
The idea is that specifying any prop from identifiers if one of props already passed to react component should give an error and there should be no suggested other props (if possible). Is there are elegant way to to create type for that?
P.S. I hope this is not a duplicate, but I was unable to find exactly what I want as other solutions require union from many types where other props set to never instead of omitting them or just made optional.
CodePudding user response:
To create a utility type that takes in a type with multiple properties and produces an union of types where each individual member corresponds to one of the keys of the original multi-property type, you could do something like this:
type Split<T> = { [K in keyof T]: Pick<T, K> }[keyof T]
This will mean the following:
type Foo = {
a: A,
b: B,
c: C
}
type Bar = Split<Foo>
// ~~~
// Bar = Pick<Foo, "a"> | Pick<Foo, "b"> | Pick<Foo, "c">
// = { a: A } | { b: B } | { c: C }
Now, I understand you want IntelliSense to not suggest the other properties, like it does here:
type Foo = { a: number, b: string }
const foo: Split<Foo> = { a: 0, … }
// ^
// | (property) b: string
But I'm not aware of a way to make TypeScript totally ignore the existence of b: String
, since { a: number, b: string } extends { a: number } | { b: string }
; as long as the shape of a value satisfies the type assigned to it, TypeScript will allow it. The TypeScript language service in your editor is "smart enough" to know to suggest the possible properties to you — it's not directly related to the type system itself and you cannot influence it in this case.
Since you wanted to have the other properties set to never
in the union, you could do something like:
type Split<T> = {
[K in keyof T]:
Pick<T, K> &
Partial<
Record<
Exclude<keyof T, K>,
never
>
>;
}[keyof T]
Now you'll have:
type Foo = {
a: A,
b: B,
c: C
}
type Bar = Split<Foo>
// ~~~
// Bar = (Pick<Foo, "a"> & Partial<Record<"b" | "c", never>>)
// | (Pick<Foo, "b"> & Partial<Record<"a" | "c", never>>)
// | (Pick<Foo, "c"> & Partial<Record<"a" | "b", never>>)
// = ({ a: A } & { b?: never, c?: never })
// | ({ b: B } & { a?: never, c?: never })
// | ({ c: C } & { a?: never, b?: never })
// = ({ a: A, b?: never, c?: never })
// | ({ b: B, a?: never, c?: never })
// | ({ c: C, a?: never, b?: never })
At least with the current TSC version, the errors this approach produces seem to be misleading and generally hard to read anyhow. I found another post with this problem addressed by Jcalz; they have a more elegant solution: https://stackoverflow.com/a/57576688/11308378
CodePudding user response:
To create a type that allows only one of the properties from the Identifiers
type to be specified, you can use the Pick
type from TypeScript. Pick
takes a type and one or more keys from that type, and creates a new type that includes only the specified keys.
For example, if you want to create a type that allows only the alias
property from Identifiers
to be specified, you can use Pick
like this:
type AliasIdentifier = Pick<Identifiers, "alias">;
You can then use this new AliasIdentifier
type wherever you need to specify only the alias property from Identifiers
. For example, you could use it as the type for a React component prop like this:
function MyComponent(props: { identifier: AliasIdentifier }) {
// ...
}
Now, if you try to pass an Identifiers
object to the identifier
prop of MyComponent
, TypeScript will give an error, because MyComponent expects an object with only the alias
property, not the full Identifiers
object.