Pull to refresh

Comments 20

Было бы неплохо увидеть аналогичные примеры для Angular и Vue.

Эту архитектуру можно во Vue интегрировать по аналогии с React. Для этого подписка может быть в методе created, а отписка от изменений beforeDestroy. Сами сервисы можно передавать через метод provide по аналогии с контекстом.
По Angular написать не могу, так как опыта с ним не имел.

Не нравится мне ваш интерфейс Emmitable, потому что для него невозможно создать легковесную проекцию или фильтр:


class Foo {
    constructor(private bar: Bar) {}
    on(event: …, cb: …) {
        this.bar.on(event, e => cb(e.baz));
    }
    off(event: …, cb: …) {
        this.bar.off(event, ???);
    }
}

Ну и плюс метод emit тут совершенно лишний, не дело выставлять генерацию событий наружу.


Предлагаю рассмотреть следующий интерфейс:


interface Emmitable<E> {
    on<K extends keyof E>(event: K, cb: (event: E[K]) => void): () => void;
}

Ну или вот так, если не нравятся ничего не говорящие типы-функции:


interface EventSubscription {
    off(): void;
}

interface Emmitable<E> {
    on<K extends keyof E>(event: K, cb: (event: E[K]) => void): EventSubscription;
}

Можно сделать интерфейс:


interface Eventable<E> {
    on<K extends keyof E>(event: K, cb: (event: E[K]) => void): void;
    off<K extends keyof E>(event: K, cb: (event: E[K]) => void): void;
}

Который будут реализовывать сервисы, а само управление подписками вынести в отдельный класс Emitter, который будет через композицию использоваться в сервисах:


interface NoteEvents {
    change: undefined;
}

class NoteService implements Eventable<NoteEvents > {
    emitter = new Emitter<NoteEvents>();

    on<K extends keyof E>(event: K, cb: (event: E[K]) => void): void {
        this.emitter.on(event, cb);
    }

    off<K extends keyof E>(event: K, cb: (event: E[K]) => void): void {
        this.emitter.off(event, cb);
    }
}

И тогда класс сервиса будет отвечать только за сам сервис, а подписки будут реализованы в другом классе.

В целом всё понравилось, но вот в конце когда мы в реакт контекстом передаём сервисы. Это такой ServiceLocator получается со всеми вытекающими. React context средство мощное, но я бы с помощью его бизнес логику внутрь реакта не заносил.
Чтобы уменьшить связанность, стоит связывать вызовы реакта с сервисами где-то на уровне монтирования, ну или через посредника/диспетчера/шину событий.

С другой стороны, если это сделано осознанно, как компромисс, то почему бы и нет. Архитектура всегда лучше хаоса.
ну или через посредника/диспетчера/шину событий

Но это же ещё хуже, чем ServiceLocator…


Кстати, ServiceLocator у автора не простой, а типизированный: там перечислены все возможные сервисы. Такой подход всё ещё не помогает выделять изолированные компоненты, но хотя бы помогает избежать ситуации "компонент запрашивает несуществующий сервис".

UFO just landed and posted this here

Но это не отвяжет от сервис-локатора тот компонент, который будет использовать компонент созданный HOC.

UFO just landed and posted this here

Ну так в том-то и проблема, что не знает. Соответственно, неизвестно будет ли компонент работать если этого сервиса не будет в контексте.

UFO just landed and posted this here

Возьмите свой же пример и доведите его до конца, отрендерив App напрямую на странице. Он сбилдится? Сбилдится. А работать будет? Нет, не будет, потому что провайдер забыли.

UFO just landed and posted this here

Смущает тот факт, что необходимость "корректной инициализации" не следует из типов и не проверяется компилятором.

UFO just landed and posted this here
да, есть тоже минусы

вопрос в том какие проблемы решаем и с чем можем мириться

На мой взгляд, архитектура всегда очень специфична задаче и всегда связана с деньгами.


Если затраты на начальном этапе больше и на этапе поддержки больше, какую пользу приносит эта архитектура?


Не поймите меня неправильно, я люблю архитектуру, но просто для меня аргументы показались не убедительными.


Мне показалось, что вы пытаетесь создать Redux в своей архитектуре или другой state manager, просто без привязки к оным.

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

Почему везде пиариться, что к этому нужно стремиться? Разве оно того стоит?
Предположим:
Есть приложение написаное по вашей архитектуре. «Библиотека отображения» — Реакт. Кому и при каких обстоятельствах должно стукнуть в голову мысля «А давайте выкенем реакт, и заменим библиотеку отображения на Ангуляр, или на Вью»?
Кто такое решение вдруг должен принять? Менеджер, разработчик?

Обычно ведь принимается решение переписать «ВСЁ», не так ли? Всё имеется ввиду не только отображение, а и «бизнес-логику», частично бекенд, частично инфраструктуру и т.д. И причины обычно какие-то весомые, от бизнеса идут сверху.

Но вот чтобы захотеть заменить просто библиотеку отображения… не трогая всё остальное...? Такое возможно?
Вот я и спрашиваю в начале, а стоит ли оно того? Вместо того чтобы использовать выбранную библиотеку/фреймворк на максималках, не добавлять абстракций и не усложнять.

Очередной раз вижу подход: пишем архитектуру проекта полностью независимо от %framework name%. В очередной раз вижу груду кода на ровном месте, переизобретение велосипедов (причём плохо, например ужасный Emmitable), и костыли при попытке подключить это к %framework name%. При этом от %framework name% используем 5-10%. И самое главное, теперь весь technical debt будет копиться в "наша архитектура", и когда придёт время "поменять %framework name%" в первую очередь выкинут весь код "из" и "для" "наша архитектура". Потому что оно сгниёт куда раньше. Оно уже на тривиальном примере выглядит хуже, чем, прости хоспади, проект на redux (тоже своего рода попытка полностью отделиться от %framework name%).


Я ни разу не сторонник пихать всё в одну корзину, но вот такое вот радикальное "все беды от %framework name%", поэтому не будем из него использовать ничего приводит к тому что даже hello world-ы получаются размером с морской танкер. И столько же манёвренны.


Эти обработчики должны быть чистыми и тогда они могут использоваться по всему проекту

Вы не можете написать метод .setName(newName: string): void чистым. Он ведь void.


На практике замечательно работает любая архитектура где здравый смысл был взят за основу, и решения пересматривались при возникновении проблем. Т.е. уделяли время как разгребанию технического долга, так и в целом серьёзно рефачили код при необходимости. Разделение ответственности, DRY, YAGNI и прочие мантры, будучи применёнными с умом, лишают большей части реальных, а не выдуманных проблем. Городить же фабрики над фабриками, обёртки над обёртками, исходя из: "уйти как можно дальше от богомерзкой %library name%", не имея на то реальной необходимости — это попытка решать несуществующие проблемы.


Вот на кой чёрт вам тут React, если вы не используете его state, effect-ы… Как шаблонизатор? А зачем брать такой плохонький шаблонизатор, коли уж у вас всё равно весь проект изолирован?


Если же это всё ещё и в стартапе городить, то это прямо совсем печаль будет.

Вот на кой чёрт вам тут React, если вы не используете его state, effect-ы… Как шаблонизатор? А зачем брать такой плохонький шаблонизатор, коли уж у вас всё равно весь проект изолирован?

Плохонький-то плохонький, но ведь один из двух статически типизированных же.

Sign up to leave a comment.

Articles