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.
