Я пытаюсь создать пространство имен/класс/интерфейс/тип (в зависимости от того, что мне нужно, я исследовал их все, и ни один из них, похоже, не может выполнить то, что мне нужно), в котором объявлены некоторые свойства и методы. Но также есть подтипы внутри него, такие как Character.Stats
или Character.Currency
. Все еще имея только Character
в качестве допустимого типа
Как мне нужно отформатировать/разметить мой печатный файл, чтобы этот classes.ts
файл был допустимым машинописным текстом?
// 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.info(`Attacked ${Character.name}`)
}
}
Возможно ли это даже в настоящее время с помощью машинописного текста?
Ниже моя текущая тестовая кодовая база:
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.info(`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);
Этот текущий код работает нормально, но похоже, что этот текущий макет вызовет проблемы в будущем. Из-за Items.Type
/Characters.Type
Если это самое близкое, что я могу получить к желаемому результату, то...
Суммируя...
я бы хотел что-то вроде этого
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" };
Это недопустимый код, так как let p: A
не допускает использование пространства имен в качестве типа, но, надеюсь, это отражает то, чего я пытаюсь достичь.
Хорошо, я загрузил код, который у меня есть в настоящее время. Все типы хранятся в каталоге ./types
, который импортируется из каталога classes.ts
, который создает некоторые классы, использует ключевое слово implements
, а затем экспортирует эти классы для использования в index.ts
Если я правильно понимаю, вы хотели бы иметь возможность избавиться от суффикса .Type
, чтобы можно было писать напрямую:
class Character implements Characters {
constructor(
public name: string,
public stats: Characters.Stats, // Type as a "member" of Characters
public currency: Characters.Currency
) {}
// Etc.
}
Вы озадачены, потому что не видите, как «встраивать» типы Stats
и Currency
как «члены» Characters
.
С одной стороны, если бы это был interface
, любой «встроенный» тип также рассматривался бы как член интерфейса.
С другой стороны, если бы это был namespace
, его нельзя было бы использовать непосредственно как тип.
На самом деле вы можете достичь и того, и другого одновременно, потому что namespace
позволяет объединять с другими типами.
При этом мы объявляем interface
и объединяем его с namespace
, используя то же имя.
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
}
}
Кстати, в вашем конструкторе вам не нужно явно назначать члены вашего класса, если они уже используются в качестве параметров конструктора с тем же именем и модификатором видимости, см. Свойства параметров класса:
TypeScript предлагает специальный синтаксис для превращения параметра конструктора в свойство класса с тем же именем и значением. Они называются свойствами параметров и создаются путем добавления к аргументу конструктора префикса одного из модификаторов видимости
public
,private
,protected
илиreadonly
.
Ух ты! Большое спасибо за подробный ответ, я знал, что есть лучший способ сделать это. Я видел кое-что о том, как объявить один и тот же интерфейс, чтобы объединить их, но я никогда не думал использовать эту идею для пространств имен и интерфейсов вместе. Еще раз спасибо
Также спасибо за совет по объявлению свойства класса, я еще немного почитаю об этом
БЫСТРОЕ ДОБАВЛЕНИЕ: я использовал это решение во внешнем файле ввода, но я думаю, потому что экспортируется только первое объявление символов. Есть два решения: экспортировать как export interface Character
, так и export namespace Character
Затем импортировать из другого файла, используя import { Character } from "./character.ts";
Другое решение — не иметь ключевых слов экспорта рядом с объявлениями интерфейса или пространства имен, но export default Character
в конце файла ввода и импортировать его следующим образом: import Character from "./character.ts";
Как можно построить
Character.Stats
в уме? Какой смысл в этом специальном типе, если не вводить значения? Более подробная информация о вашем варианте использования поможет определить, как должно выглядеть решение.