Комментарии 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,
};
Дружище, ты ошибся топиком.
А наследование работает? Можно сделать второй компонент отнаследовав от первого и добавив логики? )
А хуки есть?) С хуками например очень просто делать такие вещи:
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.
Преимущества:
Позволяет обмениваться состояниями между разными React приложениями на одной странице, с разными рутами, без провайдеров. Актуально для микрофронтов.
Позволяет обменивать состояниями даже между разными вкладками браузера. Редактируешь на одной вкладке в админке, на второй смотришь как выглядет в продакшене.
Обменивание состоянием не перерисовывает все дерево как в случаях с Контекстом.
Умершие компоненты перестают потреблять память на состояния.
Паттерн в целом хорошо себя показал на бекенде на микросервисах. Тут по аналогии каждый компонент это независимое приложение микрофронт и общаются они также.
?❌ 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 это позволяет реализовать без лишних движений. Равно как и позволяет обратное — просто создавай сторы из кода компонент, и вот уже ЖЦ совмещен.
Компутедов нет? Главная сложность именно от них...
Привет! Есть пару вопросов.
1. На каком уровне должны описываться индикаторы загрузки? (isLoading)
2. В redux есть хук useSelector для получения значения из стора внутри дерева компонентов, в случае с reca нужно будет это все делать через реакт контекст?
ReCA: React Clean Architecture state manager