Reputation: 18486
I want to be able to initialise a class with or without value. If you pass a value then all methods of this class will expect this value as an argument and the value will be of the type you passed with initialisation.
If you pass nothing then methods are not going to expect any arguments and the value will be undefined.
I think some code will make a better example:
class Foo<T = void> {
constructor(public type?: T) {}
bar = (type: T) => {
if (type) {
this.type = type;
}
};
}
const fooUndefined = new Foo();
fooUndefined.bar(); // no errors, this is ok
fooUndefined.type === undefined; // no errors, this is ok
fooUndefined.bar(1); // expected error, this is ok
const fooNumber = new Foo(0);
fooNumber.type === 1; // no errors, but type is `number | undefined`, this is not ok
fooNumber.type > 0; // unexpected error because type is `number | undefined`, this is not ok
fooNumber.bar(1); // no errors, this is ok
fooNumber.bar(); // expected error, need to pass number, this is ok
fooNumber.bar('1'); // expected error, strings are not acceptable, this is ok
const fooUnion = new Foo<'a' | 'b'>() // no error, unexpected because it should not allow it
fooUnion.type.charAt(0) // unexpected error
const fooUnion2 = new Foo<'a' | 'b'>('c') // expected error, this is ok
fooUnion2.type.charAt(0) // unexpected error
So the type
property in fooNumber
example is of type number | undefined
. Is there a way to narrow it to number
without explicit typecasting?
Upvotes: 4
Views: 266
Reputation: 21926
You can't actually model that this way in Typescript, because what you would need to do is overload the class constructor, and you can't. You can solve this with an overloaded factory function:
class Foo<T> {
public type: T;
constructor(type: T) { // param is no longer optional
this.type = type;
}
bar(type: T) {
if (type) {
this.type = type;
}
}
}
function makeFoo(): Foo<void>
function makeFoo <T>(type: T): Foo<T>
function makeFoo <T = void>(type?: T): Foo<T> | Foo<void> {
if (typeof type === 'undefined') {
return new Foo<void>(undefined); // explicitly pass undefined
}
return new Foo<T>(type);
}
const fooUndefined = makeFoo();
fooUndefined.bar(); // no errors
fooUndefined.type === undefined; // no errors
fooUndefined.bar(1); // expected error, this is ok
const fooNumber = new Foo(0);
fooNumber.type === 1; // no error now
fooNumber.type > 0; // no error now
fooNumber.bar(1); // no errors
fooNumber.bar(); // expected error, need to pass number, this is ok
fooNumber.bar('1'); // expected error, strings are not acceptable, this is ok
const fooUnion = new Foo<'a' | 'b'>() // error, no longer allows it
fooUnion.type.charAt(0) // no error now
const fooUnion2 = new Foo<'a' | 'b'>('c') // expected error, this is ok
fooUnion2.type.charAt(0) // no error now
Although I'm not sure how easy it would or wouldn't be to refactor your existing codebase to use such.
Upvotes: 1
Reputation: 834
All condition params get SomeType | undefined.
You can write this way to bypass it.
Casting to number
fooNumber.type as number > 2
Or tell typescript you are sure there is available value using ! expression.
fooNumber.type! > 2
Upvotes: 0