Как стать автором
Обновить

Комментарии 26

vue state manager:

import { reactive } from 'vue';

const state = reactive({

    jobs: [],

});

const actions = {

    addJob(job) {

        state.jobs.push(job);

    },

};

export default {

    state: state,

    ...actions,

};

Дружище, ты ошибся топиком.

Поверь, я попал очень больно. И Clear и Architecture и state maanger и главное чистота и выразительность

А наследование работает? Можно сделать второй компонент отнаследовав от первого и добавив логики? )

А хуки есть?) С хуками например очень просто делать такие вещи:

export const Product = (): JSX.Element => {
    const store = useStore(SomeStore);
    const {isSmallScreen} = useScreenSize();

    return (
        <Swiper
            slidesPerView={isSmallScreen ? "auto" : 2}
            spaceBetween={isSmallScreen ? "20" : "40"}
            updateOnWindowResize
        >
            ...
        </Swiper>
    )
};

Зачем хуки, если можно просто использовать классы?

export class Product extends View {
    
    @mem store() { return new SomeStore }
    isSmallScreen() { return ScreenSize.isSmall() }
    slidesPerView() { return this.isSmallScreen() ? "auto" : 2 }
    spaceBetween() { return this.isSmallScreen() ? "20" : "40" }
    
    render() {
        return (
            <Swiper
                slidesPerView={ this.slidesPerView }
                spaceBetween={ this.isSmallScreen }
            >
                ...
            </Swiper>
        )
    }
    
}

До перехода на функциональные компоненты так и делали. Но сейчас все же используем хуки. Причины:

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

  • Про хуки знает каждый джун разработчик, они подробно описаны в документации,

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

В итоге сильно понижаем порог входа не жертвую качеством.

На вопрос ниже отвечу вечером как буду посвободнее.

  • Эмуляция классов в принципе не может быть проще использования нативных средств выражения тех же абстракций.

  • А про классы он не знает и в документации они не описаны?

  • Хуки - это и есть композиция.

Выглядит интересно. Можете классифицировать своё решение по этим 12 апектам?

К ФП всё это не имеет никакого отношения всё же.

Я сыграл в твою игру по твоим правилам. К сожалению новый редактор хабры не может в таблицы, поэтому так.

Style
?✅

Watch
?❌ Вызываются не все подписчики

Dupes
?‍❌ Equality перекладывается на React

Origin
?✅

Tonus
?❌

Order
?❌

Flow
?✅

Error
?❌ Обработка ошибок на стороне пользователя и фреймворка

Cycle
?✅ Сторы не могут попасть в цикл, ибо изолированы, в крайнем случае словится лимит стека

Depth
?✅

Atomic
?❌

Extern
?✅

Но в этой либо не все вписывается в идеологию той статьи. В частности пункты Watch Dupes написаны для настоящей реактивности, а не то что в реакте. ReCA работает на событиях от пользователя, но он не рассылает изменения подписчикам, а использует механизм отрисовки реакта. Dupes аналогично, сама ReCA не занимается сравнением состояния, этим так же занимается механизм отрисовки реакта.

В целом в той статье упор идет на Mono Reaction Runtime. В ReCA же используются микро реакции с микро сторами, фактически каждый компонент является независимым микро приложением, за счет чего идеально подходит для микро фронтендов.

А, все сторы только в хуках компонента живут? Это ж очень не эффективно. Если будет стор, данные из которого нужны многим компонентам в глубине, то это будет приводить к ререндеру корневого компонента с последующим ререндером всех промежуточных из-за прокидывания данных через пропсы.

Это эффективно поскольку данные для мертвых компонентов не потребляют память, а следовательно и электричество. Нету компонента, нету стора, нету потребления памяти.

Случаи с общими данными между сторами решаются несколькими приемами:

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

  • Код организуется согласно концепции атомарного дизайна, который решает проблему как слишком крупных, так и слишком мелких компонентов.

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

Соответственно данные по дереву прокидываются только в рамках родитель-ребенок и к лишним перерисовкам не приводит.

Как будет выглядеть типовая задача: есть компонент формы, а есть независимый от неё компонент превьюхи. При изменении формы должна обновляться и превьюха.

Задача совсем не типовая, особенно с учетом что в 2022 году редактор и превью объединены. Хабр тому пример.

Но да ладно, два примера решения задачи.

1. Они имеют связь родитель-ребенок:

export const ArticleEditPage = (): JSX.Element => {
    const store = useStore(ArticleStore);

    return (
        <div>
            ...
            <WYSIWYGEditor onChange={store.handleArticleChange} />
            <WYSIWYGPreview article={store.article} />
            ...
            <Button onClick={store.handleSaveArticle}>
                Сохранить
            </Button>
        </div>
    );
}

2. Компоненты расположены очень далеко:

// Компонент редактор
export class WYSIWYGEditorStore extends AutoStore {

    public constructor (
        private readonly eventBusService: EventBusService
    ) {
        super();
    }

    public handleArticleChange(state: ArticleState): void {
        this.eventBusService.dispatch(BusEvent.ARTICLE_CHANGE, state);
    }
}

// Компонент превью
export class WYSIWYGPreviewStore extends AutoStore {

    public constructor (
        private readonly eventBusService: EventBusService
    ) {
        super();
    }

    public activate (): void {
        this.eventBusService.addEventListener(BusEvent.ARTICLE_CHANGE, this.processArticleState);
    }

    public dispose (): void {
        this.eventBusService.removeEventListener(BusEvent.ARTICLE_CHANGE, this.processArticleState);
    }

    private processArticleState (state: ArticleState): void {
        // apply new state
    }

}

Вместо EventBus можно использовать что угодно Observer, RxJS, MobX, React Context, Redux... но почему конкретно мне нравится именно EventBus.

Преимущества:

  1. Позволяет обмениваться состояниями между разными React приложениями на одной странице, с разными рутами, без провайдеров. Актуально для микрофронтов.

  2. Позволяет обменивать состояниями даже между разными вкладками браузера. Редактируешь на одной вкладке в админке, на второй смотришь как выглядет в продакшене.

  3. Обменивание состоянием не перерисовывает все дерево как в случаях с Контекстом.

  4. Умершие компоненты перестают потреблять память на состояния.

  5. Паттерн в целом хорошо себя показал на бекенде на микросервисах. Тут по аналогии каждый компонент это независимое приложение микрофронт и общаются они также.

?‍❌ Equality перекладывается на React

Тогда это: ?Identity

?❌ Instant

А что же тогда откладывается на rAF?

?✅ Async

А как прекращается отслеживание зависимостей перед await и возобновляется после?

Во всех случаях нет. Поскольку изменения пересчитываются мгновенно, а с задержкой только перерисовка. Это примерно как в компьютерных играх, видеокарта может считать 200 кадров в секунду, но ограничивают только на 60 (по частоте монитора), что бы она лишнее электричество не потребляла. В ReCA примерно тот же подход.

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

Похоже на дефолтный ченж детекшен в ангуляре. И типичная оптимизация там - выпиливание его.

Ничего общего с ченж детектором. Все гораздо проще и быстрее.

Разве только то что они оба ловят события, но они ловят разные события и обрабатывают их совершенно по разному.

ReCA не использует аналог Zone.JS и манкипатчинга, не использует обработку состояний, не использует глобальный Reaction Runtime.

Вместо этого ловится события вызова логики стора, все сторы независимы, происходит только локальное обновление компонента.

Или я чего-то не понимаю, или кто-то изобрёл mobx?

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

В частности он не отвечает на вопрос "А где хранить пере используемую в сторах логику?" и имеет проблему с жизненным циклом. В Reca все эти проблемы решены.

А именно:

  • Встроен DI, теперь переиспользуемую логику можно выносить в сервисы, фронтам может быть знакомо по Angular и NestJS,

  • Сильно упрощен механизм инъекции стора в компоненты,

  • Лишен проблем с жизненным циклом,

  • Улучшенный механизм перерисовки, что хорошо сказывается на производительности на слабых устройствах,

  • Легко кастомизируется, можно легко создать свою реализацию стора отнаследовав от базового стора.

MobX остановился на реактивности, а не на сторах. Сторы — это mobx-react, а не mobx.
А остановился он там потому, что гораздо лучше делать одну вещь хорошо, чем 20 вещей средненько.


  • DI может быть нужен, а может быть и совершенно излишен. Реактовский context вполне справляется с проектами даже энтерпрайзово-среднего размера (десятки kLOC).
  • Что может быть проще "как угодно"? Именно так сторы MobX внедряются в компоненты.
  • ЖЦ сторов совсем не обязан как-то совпадать с ЖЦ компонентов, и MobX это позволяет реализовать без лишних движений. Равно как и позволяет обратное — просто создавай сторы из кода компонент, и вот уже ЖЦ совмещен.

Компутедов нет? Главная сложность именно от них...

По умолчанию все методы являются computed, если на них нету декоратора @notRedraw. Функцию сравнения состояний делегирована механизму перерисовки реакта.

Привет! Есть пару вопросов.
1. На каком уровне должны описываться индикаторы загрузки? (isLoading)
2. В redux есть хук useSelector для получения значения из стора внутри дерева компонентов, в случае с reca нужно будет это все делать через реакт контекст?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории