I am trying to create a namespace/class/interface/type (whichever I need, I've researched all of them and none seem to be able to fulfill what I need), which has some properties and methods typing declared. But also have subtypes within it like Character.Stats
or Character.Currency
. While still having just Character
as a valid type
How would I need to format/layout my typing file to allow this classes.ts
file to be valid typescript?
// statement to import Character type
class Player implements Character {
constructor (
public name: string, // a property
public stats: Character.Stats, // a property with a subtype typing
public currency: Character.Currency
) {
this.name = name
this.stats = stats
this.currency = currency
}
attack(target: Player) { // parameter is typing of its own class
console.log(`Attacked ${Character.name}`)
}
}
Is this even currently possible with typescript?
Below is my current test codebase:
types/items.ts
export interface Type {
name: string,
sprite: string,
}
types/characters.ts
import * as Classes from "../classes";
export interface Stats {
health: number,
attack: number,
defence: number,
}
export interface Currency {
gold: number,
ridium: number
}
export interface Type {
name: string,
stats: Stats
currency: Currency
announce(sentence: string): void,
attack(target: Type): void,
heal(item: Classes.HealingItem): void
}
classes.ts
import * as Characters from "./types/characters";
import * as Items from "./types/items"
export class Character implements Characters.Type {
constructor(
public name: string,
public stats: Characters.Stats,
public currency: Characters.Currency
) {
this.name = name;
this.stats = stats
this.currency = currency
}
announce(sentence: string) {
console.log(`I, ${this.name}, ${sentence}`)
}
attack(target: Character): void {
this.announce(`am going to attack ${target.name}`)
}
heal(item: HealingItem) {
this.stats.health = item.healthPoints;
this.announce(`used ${item.name} to heal my hp from ${this.stats.health - item.healthPoints} to ${this.stats.health}`)
}
}
export class HealingItem implements Items.Type {
constructor(
public name: string,
public sprite: string,
public healthPoints: number
) {
this.name = name
this.sprite = sprite
this.healthPoints = healthPoints
}
}
index.ts
import * as Classes from "./classes";
const hero = new Classes.Character("Bob", // name
{ // stats
health: 100,
attack: 10,
defence: 25
},
{ // currency
gold: 50,
ridium: 0
}
)
const apple = new Classes.HealingItem("Apple", "./sprites/apple.sprite", 25);
hero.heal(apple);
hero.attack(hero);
This current code all works fine, but it seems like this current layout will cause issues in the future. Due to the Items.Type
/Characters.Type
If this is the closest I can get to the result I would like, then...
In short...
I would like something like this
interface B {
x: string
}
namespace A {
export type v1 = number
export type v2 = boolean
export type v3 = B
}
let p: A = {
v1: 9,
v2: true,
v3: { x: "some string" }
};
let q: A.v1 = 2;
let r: A.v2 = false;
let s: A.v3 = { x: "strung" };
This is not valid code as let p: A
doesn't allow a namespace as a type, but hopefully this portrays what I am trying to achieve.
CodePudding user response:
If I understand correctly, you would like to be able to get rid of the .Type
suffix, so that you could write directly:
class Character implements Characters {
constructor(
public name: string,
public stats: Characters.Stats, // Type as a "member" of Characters
public currency: Characters.Currency
) {}
// Etc.
}
You are puzzled because you do not see how to "embed" Stats
and Currency
types as "members" of Characters
.
On the one hand, if it were an interface
, any "embedded" type would be seen as a member of the interface as well.
On the other hand, if it were a namespace
, it could not be used directly as a type itself.
You can actually achieve both at the same time, because namespace
enables merging into other types.
With this, we declare the interface
, and we merge it with a namespace
using the same name.
export interface Characters {
name: string
stats: Characters.Stats
currency: Characters.Currency
announce(sentence: string): void
attack(target: Characters): void
heal(item: HealingItem): void
}
// Merge the namespace with the type of the same name
namespace Characters {
// Make sure the "embedded" types are explicitly exported
export interface Stats {
health: number
attack: number
defence: number
}
export interface Currency {
gold: number
ridium: number
}
}
BTW, in your constructor, you do not need to explictly assign your class members if they are already used as constructor parameters with the same name and a visibility modifier, see Class Parameter Properties:
TypeScript offers special syntax for turning a constructor parameter into a class property with the same name and value. These are called parameter properties and are created by prefixing a constructor argument with one of the visibility modifiers
public
,private
,protected
, orreadonly
.