Reputation: 16627
I have a class like this
class Schema<T extends "foo" | string[]> {
constructor(public value: T) {}
}
for which the constructor should accept the string literal "foo" or an arbitrary array of strings. An instantiation should return an object which is typed with only the literals (also for the array as union literals):
// What I would like to have
new Schema(["x", "y"]) // => Schema<"x" | "y">
new Schema("foo") // => Schema<"foo">
This is not the case for the above class implementation (it returns an object of type Schema<string[]>
when providing an array).
But if I define my class this way
class Schema<T extends "foo" | string> {
constructor(public value: T[]) {}
}
I lose the option to call new Schema("foo")
.
Is there some Typescript KungFu to solve this problem?
Upvotes: 1
Views: 181
Reputation: 33051
Here you have most simpliest way:
class Schema<Value extends string, Values extends string[]> {
constructor(value: Value | [...Values]) { }
}
// What I would like to have
new Schema(["x", "y"]) // Schema<string, ["x", "y"]>
new Schema("foo") // Schema<"foo", string[]>
As you have already noticed, there is a problem with extra string
or string[]
generic.
I think we can do better.
SInce constructor
does not allow us to use generics, we can create extra method
for inference purpose.
class Schema {
register<Value extends string>(value: Value): asserts this is { value: Value }
register<Value extends string[]>(value: [...Value]): asserts this is { value: Value[number] }
register<Value extends string | string[]>(value: Value): asserts this is { value: Value } {
}
}
const result: Schema = new Schema();
result.value // expected error
result.register(['x', 'y']);
result.value // x | y
Here you can find docs about asserts
and here about function overloads.
Please let me know if it works for you and if it needs to be improved. THank you
Upvotes: 1