Как сделать так, чтобы интерфейс содержал подтипы, а сам по себе был допустимым типом

Я пытаюсь создать пространство имен/класс/интерфейс/тип (в зависимости от того, что мне нужно, я исследовал их все, и ни один из них, похоже, не может выполнить то, что мне нужно), в котором объявлены некоторые свойства и методы. Но также есть подтипы внутри него, такие как 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 не допускает использование пространства имен в качестве типа, но, надеюсь, это отражает то, чего я пытаюсь достичь.

Как можно построить Character.Stats в уме? Какой смысл в этом специальном типе, если не вводить значения? Более подробная информация о вашем варианте использования поможет определить, как должно выглядеть решение.

Sean Vieira 18.10.2022 03:10

Хорошо, я загрузил код, который у меня есть в настоящее время. Все типы хранятся в каталоге ./types, который импортируется из каталога classes.ts, который создает некоторые классы, использует ключевое слово implements, а затем экспортирует эти классы для использования в index.ts

Matthew Basford 18.10.2022 03:18
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
2
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если я правильно понимаю, вы хотели бы иметь возможность избавиться от суффикса .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.

Ух ты! Большое спасибо за подробный ответ, я знал, что есть лучший способ сделать это. Я видел кое-что о том, как объявить один и тот же интерфейс, чтобы объединить их, но я никогда не думал использовать эту идею для пространств имен и интерфейсов вместе. Еще раз спасибо

Matthew Basford 18.10.2022 10:39

Также спасибо за совет по объявлению свойства класса, я еще немного почитаю об этом

Matthew Basford 18.10.2022 10:44

БЫСТРОЕ ДОБАВЛЕНИЕ: я использовал это решение во внешнем файле ввода, но я думаю, потому что экспортируется только первое объявление символов. Есть два решения: экспортировать как export interface Character, так и export namespace Character Затем импортировать из другого файла, используя import { Character } from "./character.ts"; Другое решение — не иметь ключевых слов экспорта рядом с объявлениями интерфейса или пространства имен, но export default Character в конце файла ввода и импортировать его следующим образом: import Character from "./character.ts";

Matthew Basford 18.10.2022 13:01

Другие вопросы по теме