Home > other >  typescript resolving not actually optional optional property
typescript resolving not actually optional optional property

Time:10-15

I'm new to typescript to bear with me here if this is not how things are supposed to work.

I have a couple of goals in converting this js to ts.

Item = {}

Item.buy = function (id) {}
Item.sell = function (id) {}

I'm trying to get intellisense to autocomplete on Item. either buy or sell. I would also want to use dot notation to create these methods in arbitrary files without putting everything in the initial bracket. So I have something like this:

interface Item {}
const Item: Item = {};

interface Item {
  buy?: Function
}
Item.buy = function () {
  Item.render()
  return "bought"
}

interface Item {
  sell?: Function
}
Item.sell = function () {
  Item.render()
  return "sold"
}

interface Item {
  render?: Function
}
Item.render = function () {
    return 1
}

The problem here now is that render is an optional property and hence I get this error:

Cannot invoke an object which is possibly 'undefined'.

How can I make ts not check for this error? Since Item is not a class there's only ever going to be 1 item and it'll definitely have the render method, there is not ever going to be an instance where that error checking is useful. Or to put it another way, it's not actually optional, I only set it to be optional to work around const Item: Item = {}; erroring if I don't have it be optional.

Is there a way to let ts know that or use a different pattern in the first place?

CodePudding user response:

SOLUTION 1:

Since you have not defined any method inside Item

interface Item {}

So you can check whether render method exist or not on Item as:

Item.buy = function () {
  if(Item.render) Item.render();  // CHANGE
  return "bought";
}

SOLUTION 2:

Best solution would be to add type of render on interface Item as:

interface Item {
    render: () => void;
}

and then you can use it as:

Item.buy = function () {
  Item.render();
  return "bought";
}

CodePudding user response:

My inclination here would be to use namespaces instead of an interface to hold these functions. It could look like this:

namespace Item {
  export const buy = function () {
    Item.render()
    return "bought"
  }
}

namespace Item {
  export const sell = function () {
    Item.render()
    return "sold"
  }
}

namespace Item {
  export const render = function () {
    return 1
  }
}

Then you'd be able to access them the same way, as methods on the singleton Item value:

// elsewhere
console.log(Item.sell()); // "sold"

Note that namespace is a TypeScript specific feature, and nowadays new code is generally encouraged to use modules instead where possible. I don't really know if there's a good way to get this sort of behavior with modules, because the part we're using, merging different things into a common JS value, is not really how modules works. Maybe declaration merging and importing would give this to you, but I don't know.

Anyway, as long as you're okay with a TS-specific feature, then namespace would be an idiomatic way to represent this sort of gradual building of a singleton.

Playground link to code

  • Related