Комбинируйте примеси с дженериками в node.js

Я работаю над библиотекой, в которой я хотел бы поддерживать несколько разных расширений для одних и тех же библиотечных методов.
Вот мой укороченный код примеси, чтобы дать вам представление:

type Creator<T = {}> = new (...args: any[]) => T;
class DefaultCreator {
        world:string[] = [];
        someMagic() {
            this.world.push("magic")
        }
        getWorldList() {
            return this.world;
        }
}

function PlantCreator<TBase extends Creator>(Base: TBase) {
    return class extends Base {
        private world = (<DefaultCreator><any>this).world;
        createPlant() {
            this.world.push("Plant")
        }
    };
}

function AnimalCreator<TBase extends Creator>(Base: TBase) {
    return class extends Base {
        private world = (<DefaultCreator><any>this).world;
        createDog() {
            this.world.push("Dog")
        }
    };
}

Я использую это так:

const MyWorld = AnimalCreator(PlantCreator(DefaultCreator));
const world = new MyWorld();
world.someMagic();
world.createPlant();
world.createDog();

Теперь мой вопрос: как я могу создать класс, взяв «Мой мир» сверху?

abstract class Playground {
    abstract createWorld(creator: DefaultCreator);
    play() {
        this.createWorld(new DefaultCreator());
    }
}

Моя идея заключается в том, что реализация может использовать функции фреймворка (здесь просто играйте) и создает мир с помощью настроенного Создателя (он же Builder). Я пробовал дженерики, но они не компилируются. Что я делаю неправильно?

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
0
73
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Кажется, что это невозможно. Поскольку MyWorld не является типом, это скорее странное описание типа.

Когда я позволяю своей идее указать тип, я получаю эту строку:

const MyWorld: { new(): ({ world: string[]; createDog(): void } & any); prototype: { world: string[]; createDog(): void } } = AnimalCreator(PlantCreator(DefaultCreator));

Мое решение состоит в том, чтобы заставить пользователя по соглашению создать экземпляр пользователем и вернуть world моего примера.

Я изменил свою игровую площадку, чтобы получить этот код:

abstract class Playground {
    abstract createWorld(): string[];
    play() {
        console.info("Creating world containing:");
        this.createWorld().forEach(item => console.info(`- ${item}`))
    }
}

class MyPlayground extends Playground {
    createWorld(): string[] {
        const world = new MyWorld();
        world.someMagic();
        world.createPlant();
        world.createDog();
        return world.getWorldList();
    }
}

new MyPlayground().play();

Вывод кода выше:

Creating world containing:
- magic
- Plant
- Dog

Вы на самом деле были не за горами, но вы размыли границы между Типами и Ценностями. К счастью, Javascript/Typescript позволяет это сделать.

// Match only a constructor
type Creator<T = {}> = new (...args: any[]) => T;

// Match an actual class
interface CreatorClass<T> {
    new(...args: any[]): T;
}

class DefaultCreator {
        world:string[] = [];
        someMagic() {
            this.world.push("magic")
        }
        getWorldList() {
            return this.world;
        }
}

function PlantCreator<TBase extends Creator>(Base: TBase) {
    return class extends Base {
        private world = (<DefaultCreator><any>this).world;
        createPlant() {
            this.world.push("Plant")
        }
    };
}

function AnimalCreator<TBase extends Creator>(Base: TBase) {
    return class extends Base {
        private world = (<DefaultCreator><any>this).world;
        createDog() {
            this.world.push("Dog")
        }
    };
}

interface IPlantWorld {
    createPlant(): void;
}

interface IAnimalWorld {
  createDog();
}

const MyWorld: CreatorClass<IPlantWorld & IAnimalWorld> = AnimalCreator(PlantCreator(DefaultCreator));

abstract class Playground {
    // I want to have a reference of the class' constructor
    createOtherWorld<T>(creator: Creator<T>) {
        return new creator();
    }

    // I want to reference the class itself
    createWorld<T>(creator: CreatorClass<T>): T {
        return new creator() as T;
    }
    play() {
        this.createWorld(DefaultCreator);
    }
}



class EverythingPlaygroundFactory extends Playground {
    play() {
        // provide the type information
        return this.createWorld<IAnimalWorld & IPlantWorld>(MyWorld);
    }
}

let pg = new EverythingPlaygroundFactory();
let world = pg.createWorld(MyWorld);
world.createPlant();
world.createDog();

pg.createOtherWorld(MyWorld.prototype.constructor);

Вероятно, это больше похоже на то, что вы искали.

Что следует отметить:

type Creator<T = {}> = new (... args: any[]) => T может и будет ссылаться только на фактический конструктор любого класса, но никогда не будет соответствовать всему классу/объекту класса. Всегда помните, что классы/функции являются исполняемыми объектами.

Вы предлагаете интересное решение. Итак, вы помещаете в интерфейс только конструктор для создания новых экземпляров, верно? Если это правда: крутой трюк! Однако вы упустили тот факт, что Playground является абстрактным классом, каждая пользовательская реализация должна использовать своих собственных создателей. Когда я изменяю вашу функцию createWorld, я не могу использовать методы, определенные в PlantCreator и PlantCreator. Есть ли тоже решение?

rekire 31.03.2019 20:21

Я вернусь к вам через минуту. нужно возиться

Rachael Dawn 31.03.2019 20:22

Это должно решить проблему типа. Тем не менее, я настоятельно рекомендую пересмотреть свой шаблон проектирования, поскольку вы пытаетесь объединить фабрики и экземпляры, созданные фабрикой, в одно целое. Но одно наблюдение, которое я сделал, заключается в том, что всякий раз, когда вы «не видите» метод в Intellisense, это происходит потому, что не было доступной информации о типе во время компиляции. Вы можете решить эту проблему, определив информацию о типе в интерфейсе и указав ее в качестве аннотации возвращаемого типа.

Rachael Dawn 31.03.2019 20:38

Теперь вы ограничили игровую площадку растениями, я не могу создавать животных. Вот почему мне нравится идея примесей, которые обеспечивают своего рода полиморфизм, и декораторов, с помощью которых я могу вызывать все, что захочу, для одного объекта. Мой пример выше был не очень хорош, как я вижу сейчас.

rekire 31.03.2019 21:00

Я рекомендую пересмотреть свой дизайн. Вы пытаетесь получить информацию времени компиляции, созданную во время выполнения. Если вы хотите объединить типы, вы можете использовать оператор &, который говорит: «Все оба типа доступны в этом».

Rachael Dawn 31.03.2019 21:02

Я обновил пример кода, чтобы продемонстрировать типы объединения и то, как сделать createDog и createPlant доступными. Теперь вы должны быть в состоянии экстраполировать все, что вам нужно понять из примера.

Rachael Dawn 31.03.2019 21:09

Я обновил свой ответ решением, которое у меня было, просто если вы хотите его сравнить. Я еще раз просмотрел ваш код, это ясный и не с моей точки зрения классический способ с интерфейсами, которые, на мой взгляд, не являются накладными расходами. Я думаю, что возьму свое собственное решение. Однако вы получите мой голос, так как мне нравится идея с оператором &.

rekire 31.03.2019 21:24

Да, это лучший способ сделать это ☺️

Rachael Dawn 31.03.2019 21:24

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