Danila
Danila

Reputation: 18486

Class constructor optional parameter

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?

Playground link

Upvotes: 4

Views: 266

Answers (2)

Jared Smith
Jared Smith

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.

Playground

Upvotes: 1

Vinibr
Vinibr

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

Related Questions