Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Нужен ли здесь useEffect? «хороший» React-вариант и мол-вариант здесь один и тот же код

Дата публикации: 07-07-2026 22:43:10

На Хабре вышла хорошая статья «Нужен ли здесь useEffect?»: 12 сценариев из code review, в которых эффект либо не нужен, либо нужен не такой.Я пишу на $mol и читал её как этнограф. в моле нет API эффектов. Во что превратятся все 12 демо, если их переписать ? Посмотрите ?. Сделал страницу с демками.Спойлер: чек-лист из 12 строк в конце схлопнулся в … Узнать про useEffect в $mol

Основное содержимое страницы с новостью.

Здравствуйте, товарищи! На Хабре вышла хорошая статья «Нужен ли здесь useEffect?» Виктора Горбачёва ( @Viktoriko ) : 12 сценариев из code review, в которых эффект либо не нужен, либо нужен не такой. Разбор я особо конечно не могу понять, так как пишу только на…

$mol’e и читал статью как этнограф. Каждое демо начинается с вопроса «а эффект здесь вообще нужен?», а мне этот вопрос не задать ведь в моле нет API эффектов. Стало интересно переписать. Переписал. Получилась страница с демками и этот текст.

Два места вместо трёх

У кода в React три места выполнения. Рендер для чистых вычислений, обработчик события для действий пользователя, эффект для синхронизации с внешней системой. И два вопроса перед каждым useEffect: можно ли это значение вычислить, и почему этот код должен выполниться.

В моле мест два. Вычисление, то есть обычный метод компонента: если результат стоит кэшировать, вешаешь на него @$mol_mem, и значение пересчитывается только когда меняется что-то из прочитанного внутри. И действие: декоратор @$mol_action, на который в view.tree вешается клик. Рендер отдельным местом не считается, он сам такое же вычисление. Синхронизация с внешним миром, забегая вперёд, тоже.

Пройдём по всем 12 демо. Нумерация и названия частей как у Виктора

Часть I. Не храните то, что можно вычислить

01. Производное состояние

fullName в useState, синхронизируемый эффектом — антипаттерн, правильно вычислять его во время рендера. В моле правильный вариант единственный, производное состояние просто негде хранить:

full_name() {
	return `${ this.first_name() } ${ this.last_name() }`
}

Метод, даже без mem. Кто прочитал full_name, тот перерисуется при смене имени. Запомните это совпадение: «хороший» React-вариант и мол-вариант здесь один и тот же код, дальше так будет ещё не раз.

02. Кэш дорогого вычисления

Дорогой расчёт в React мемоизируют через useMemo, перечислив зависимости в массиве. В моле это метод из прошлого демо, только с декоратором:

@ $mol_mem
visible_todos() {
	// какой-то измеримо дорогой расчёт
	const active = this.filter() === 'active'
	return this.todos().filter( t => active ? !t.done : true )
}

Пересчёт идёт только при смене фильтра или списка, в демо это видно по console.log. Дилемма «лишний useState или useMemo» не возникает, mem и есть мемоизация. Массива зависимостей тоже нет: mem запоминает, что читал filter() и todos(), по факту чтения.

03. Полный сброс состояния при смене сущности

Сброс черновика при смене userId в React решается пересозданием поддерева через key. В моле состояние можно хранить сразу по ключу:

@ $mol_mem_key
comment( user: string, next?: string ) {
	return next ?? ''
}

@ $mol_mem
current_comment( next?: string ) {
	return this.comment( this.user(), next )
}

У каждого пользователя свой черновик. Переключился на Бориса, вернулся к Анне а её недописанный комментарий на месте. В React-версии черновик при возврате не выживал, там это отдельная фича, которую надо проектировать, а тут оно само получается.

04. Корректировка состояния при смене данных

Виктор советует хранить selected_Id, а объект вычислять через find, без эффекта. Спорить не о чем, его правильный вариант уже готовый мол-код:

selection() {
	return this.items().find( i => i.id === this.selected_id() ) ?? null
}

Удалили выбранный элемент, selection сам стал null, без дополнительного обновления состояния.

Часть II. События — не эффекты

05. Общая логика обработчиков

Уведомление «Добавлено в корзину» через эффект от product.isInCart срабатывает и тогда, когда корзина восстановилась из хранилища. Общую логику Виктор выносит в обычную функцию, и это ровно то, что пишется в моле:

buy() {
	this.cart_count( this.cart_count() + 1 )
	this.status( `В корзине товаров: ${ this.cart_count() }` )
}

@ $mol_action
to_cart() {
	this.buy()
}

@ $mol_action
buy_now() {
	this.buy()
	this.status( 'Переходим к оплате…' )
}

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

06. Отправка формы

Сигнальное состояние, эффект на него, а в React 19 Actions и useActionState на два десятка строк. В моле запрос пишется прямо в действии, синхронным по виду кодом:

@ $mol_action
submit() {
	this.sending( true )
	this.$.$mol_wait_timeout( 800 ) // тут был бы запрос
	this.count( this.count() + 1 )
	this.result( `Создан пользователь #${ this.count() }` )
	this.sending( false )
}

Под капотом файбер (fiber): на время ожидания метод приостанавливается, кнопка задизейблена через sending, потом появляется результат. useActionState кстати неплохой API, pending и результат лежат рядом с причиной. Просто в моле это свойство любого действия, а не отдельная фича форм.

07. Цепочки эффектов

Игру с картами, где три эффекта триггерят друг друга, Виктор сворачивает в одну чистую функцию перехода. Мол-версия выглядит так же:

@ $mol_action
place_card( gold: boolean ) {
	this.game( this.next_state( this.game(), gold ) )
}

В React такой каскад собрать легко, он даже работает, поэтому и попадает в прод. В моле его нельзя собрать: эффектов нет, а состояние меняется в действиях, одним переходом.

08. Уведомление родителя

Ребёнок сообщает родителю через onChange, и вопрос лишь в том, звать колбэк из эффекта (плохо) или из обработчика (хорошо). В моле этот код не пишется вовсе, связь описывается в view.tree:

<= Toggle $mol_check_box
	checked? <=> light_on? false
<= Status $mol_view
	sub / <= light_status

<=> это двусторонний биндинг: тумблер и статус читают одну ячейку light_on. Ни колбэка, ни двух кругов обновления, ни выбора между controlled и uncontrolled. Если код выглядит “страшно” или непонятно, то гляньте сюда view.tree это просто краткая запись для нашего любимого JS

код выше будет представлен так

		light_on(next){
			if(next !== undefined) return next;
			return false;
		}
		Toggle(){
			const obj = new this.$.$mol_check_box();
			(obj.checked) = (next) => ((this.light_on(next)));
			return obj;
		}
		Status(){
			const obj = new this.$.$mol_view();
			(obj.sub) = () => ([(this.light_status())]);
			return obj;
		}
		sub(){
			return "\t";
		}

Часть III. Внешний мир и жизненный цикл

09. Инициализация приложения

Глава про пустой массив зависимостей, Strict Mode с двойным вызовом и guard-флаг в точке входа. В моле её заменяет одно свойство рантайма: всё лениво. Никакой мем не вычисляется, пока его кто-нибудь не прочитает.

@ $mol_mem
visits() {
	return Number( this.$.$mol_state_local.value( 'bog_effects_visits' ) ?? 0 )
}

В демо панель со счётчиком не считается, пока её не раскрыли. Вопрос «когда выполнить инициализацию ?» превращается в «кому нить нужны эти данные ?». Если никому то она не выполнится вообще

10. Подписка на внешний store

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

@ $mol_mem
online( next?: boolean ) {
	if( next !== undefined ) return next
	const win = this.$.$mol_dom_context
	win.ononline = ()=> this.online( true )
	win.onoffline = ()=> this.online( false )
	return win.navigator.onLine
}

Подписка происходит при первом чтении, дальше браузер пушит значение через гетеро-сеттер, читатели перерисовываются. Часы в демо ещё короче: $mol_state_time.now(1000) из коробки, причём таймер там живёт, только пока часы кто-то читает исходник на 25 строк всего.

11. useEffectEvent

Моё любимое демо. Соединение с чатом зависит от roomId, но не должно пересоздаваться от смены theme, и чтобы вынести тему из зависимостей эффекта, в React 19.2 появился отдельный API. В моле зависимости не декларируются, они собираются по факту чтения:

@ $mol_mem
connection() {
	const room = this.room()
	const conn = Object.assign( new $mol_object2, { room } )
	conn.destructor = ()=> console.log( 'disconnect', room )
	return conn
}

notice() {
	return `Комната «${ this.connection().room }», тема: ${ this.theme() }`
}

Тело connection читает только room, значит соединение зависит только от комнаты. Тема читается в notice, её смена перерисовывает уведомление, не трогая соединение. А при смене комнаты старое соединение закрывает деструктор. На демо-странице есть счётчик переподключений, пощёлкайте тему и посмотрите на него.

useEffectEvent это ручное исключение из ручного списка зависимостей. Когда списка нет, исключать не из чего.

Часть IV. Загрузка серверных данных

12. Четыре подхода к fetching

Здесь Виктор честно пишет: загрузка по query это настоящая синхронизация с сервером, эффект не концептуальная ошибка, опасна наивная реализация. Гонка ответов, отмена, кэш, ретраи. Дальше четыре подхода на выбор: ручной хук с AbortController, TanStack Query, use() с Suspense, RTK Query.

Мол-вариант один:

@ $mol_mem
search() {
	const q = this.query().trim().toLowerCase()
	if( !q ) return []
	this.$.$mol_wait_timeout( 600 ) // имитация сети запрос через $mol_fetch
	return this.products().filter( p => p.toLowerCase().includes( q ) )
}

Пока «сеть» отвечает, вычисление приостановлено, ожидание мол показывает сам. Пользователь напечатал новую букву — устаревшее вычисление отменяется, а с ним и запрос: $mol_fetch в деструкторе дёргает AbortController (исходник). Гонка невозможна, кэш это сам mem. В демо есть счётчик завершённых «запросов»: печатайте хоть по буквам, он почти не двигается.

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

Чек-лист

Статья Виктора заканчивается таблицей на 12 строк: задача, что проверить перед тем как писать эффект, номер демо. Хорошая таблица, я бы повесил такую рядом с доской, если бы писал на React.

Моя версия целиком:

Задача

Что делать

Вычислить значение

метод, если дорого — с @$mol_mem

Отреагировать на действие пользователя

метод с @$mol_action

Это упрощение не ради красивого финала, все 12 сценариев действительно можно разложить на эти две строки. Дерево решений не нужно.

У Виктора есть мысль, которая мне нравится: повторяющееся замечание на review надо превращать в правило команды. Это работает в любом стеке. Просто любопытно наблюдать, сколько таких правил исчезает, когда реактивность лежит в основании фреймворка, а не прикручивается к нему сверху. ( беря реакт, вы собираете свой фреймворк, по сути, а надо ли ? )

Все 12 демо живут на странице: b-on-g.github.io/bog/effects, исходники тут. Потыкайте с открытой консолью, самое интересное в ней)

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

0%Ни одного, каждый на своём месте0

0%У меня нет useEffect в проекте)0

Проголосовал 1 пользователь. Воздержавшихся нет.

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Мобильная разработка за неделю #636 (22 — 28 июня)5728-06-2026
2Когда может пригодиться экзотика в ООП: миксины/трейты/аспекты0508-07-2026
3std::expected в C++23: гайд по миграции с исключений на функциональный error handling0707-07-2026
4Попросили Claude создать WCAG-доступный DataPicker на React и потратили 3 дня на доработки0728-06-2026
5Актуальность техстека как задача: наш путь от хаоса к регулярным SLA-апдейтам5726-06-2026
6Ищу того, кто хорошо разбирается в hugo (site generator) и может плюс-минус в js/css0524-06-2026
7WP Rocket ухудшает mobile LCP-2326-06-2026
8Регулируй регулярки! Выбираем инструменты для работы с RegEx5725-11-2025
9Провожу ещё один эксперимент. Игра "Космическая данетка". Любите читать про ...0230-06-2026
10Рябков: разрабатываемые США ракеты-мишени для систем ПРО нарушают ДРСМД0026-11-2018

Классификация: Мнения. Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 5. Источник: habr.com.