Введение в любой фреймвок начинается с написания одного простого компонента. Чаще всего этим компонентом будет "счетчик нажатий". Это своеобразный "hello world" в мире фронтенд разработки. Именно поэтому я и возьму его за основу данного материала.
Когда-то давно я задался вопросом: можно ли создавать фронтенд также легко как в React, но без ререндера и скрытых слоев для вычисления состояния и обновления DOM, а только лишь с помошью конструкций самого языка JavaScript?
Решение этого вопроса и оттачивание АПИ заняло у меня несколько лет экспериментов, переписывания всего с нуля, понимания самой сущности метода и универсализации подхода.
Поэтому без лишних рассуждений хочу предоставить вам код этого компонента. Далее будут показаны три версии одного и того же копонента.
Версия 1
import { update } from '@fusorjs/dom';
const ClickCounter = (props) => {
let state = props.count || 0;
const self = (
<button click_e={() => {state++; update(self);}}>
Clicked {() => state} times
</button>
);
return self;
};
click_e
- устанавливает обработчик событий, а_e
позволяет устанавливать множество полезных параметоров, например:click_e_capture_once
, для совместимости с W3C стандартом.
Здесь функция компонента вызывается один раз при его создании, а обновление происходит при клике. Также мы извлекли состояние ("lifted state up") из самой библиотеки что позволяет использовать любую стратегию управления состоянием.
Вот как выглядит использование этого компонента:
import { getElement } from '@fusorjs/dom';
const App = () => (
<div>
<ClickCounter />
<ClickCounter count={22} />
<ClickCounter count={333} />
</div>
);
document.body.append(getElement(<App />));
Далее я подумал что мой компонент выглядит достаточно хорошо, но примерно столько же кода потребуется для его создания и в React, а нельзя ли его сделать более компактным?
Версия 2
Здесь я упрощю установку переменной состояния с помощью возможностей JavaScript по распаковке объектных аргументов функции с заданием значений по умолчанию. А также тем фактом что вторым параметром в функцию обработчика события можно передать указатель на сам объект для которого произошло событие.
const ClickCounter = ({ count = 0 }) => (
<button click_e={(event, self) => {count++; update(self);}}>
Clicked {() => count} times
</button>
);
Вот теперь я был доволен. Теперь получилось гораздо компактнее чем в React. Тем более если добавить useCallback
, справедливости ради, так как наша функция компонента выполняется единожды и не пересоздает обработчик события.
Долго-ли коротко-ли но меня осенило...
Версия 3
Ведь у нас есть универсальный синтаксис установки параметров для всех атрибутов компонента, так почему не добавить еще один параметр: update
?
const ClickCounter = ({ count = 0 }) => (
<button click_e_update={() => count++}>
Clicked {() => count} times
</button>
);
Теперь получился просто идеальный вариант. Я готов поспорить что ни в каком другом фреймворке не получится сделать более компактный переиспользуемый компонент с состоянием. Если знаете такой, напишите пожалуйста в комментариях.
Вот рабочий пример нашего компонента
Итог
Данное упражнение позволило понять что такие простые компоненты, которые содержат обработчики событий, не нуждаются в реактивных системах управления состоянием, таких как useState/Signals/Redux/Mobx... Для них достаточны обыкновенные переменные.
Вот еще один пример такого компонента:
const UppercaseInput = ({ value = "" }) => (
<input
value={() => value.toUpperCase()}
input_e_update={(event) => (value = event.target.value)}
/>
)
В терминах React это называется "managed input" компонент. Его попробовать можно тут в альтернативном стиле (не JSX).
Чтобы сократить потребление ресурсов, нужно использовать реактивные состояния только там где это необходимо. Например когда несколько компонентов в рвзных частях DOM используют одни и теже данные которые нужно менять.
Этого легко можно добиться с помощью установки одного коллбека пропсы mount
, что так же просто как использовать useState
в React. Вот тут разобран подробный пример.
Также эти ссылки могут быть вам полезны:
Спасибо за внимание!
Комментарии (13)
nihil-pro
20.05.2025 18:08В терминах React это называется "managed input" компонент. Его попробовать можно тут в альтернативном стиле (не JSX).
В вашем инпуте каретка скачет если напечатать что-то в середине текста.
isumix Автор
20.05.2025 18:08Да, есть такое, для наглядности этот компонент очень простой, в Реакт будет такое же поведение.
nihil-pro
20.05.2025 18:08Нет, в реакте такого поведения не будет.
isumix Автор
20.05.2025 18:08Вы уверены? Это стандартное поведение элемента
<input />
при присвоенииinput.value = x
.nihil-pro
20.05.2025 18:08Вы уверены?
isumix Автор
20.05.2025 18:08А что же вы опустили
toUpperCase
? С ним как раз будет точно такое же поведение.Кстати странно, я думал что это по причине присвоения, а тут что-то другое, также цифры работают корректно, надо будет покопаться.
green_fenix
20.05.2025 18:08Если я правильно понимаю, скачет когда .value дом элемента инпута меняется на не идентичную текущей строку программно.
Т.е. в этом примере скакать не будет если включить капс или вводить символы - апперкейс не меняет новую строку, так что инпут при присвоении ничего не делает - реальное значение уже изменилось когда пользователь нажал кнопку.
Но если любым программным способом присвоить другое значение, позиция каретки сбрасывается - контекст действий юзера над инпутом потерялся.
isumix Автор
20.05.2025 18:08Да, видимо так и работает. Возможно если сохранять позицию курсора и восстанавливать ее после присвоения, то проблема решится.
nin-jin
20.05.2025 18:08Именно так и работает $mol_string, но ни в коем случае не подглядывайте в $mol, продолжайте набивать шишки.
isumix Автор
20.05.2025 18:08Философия Фьзора - не трогать дефолтное поведение. Лучше один раз изучить стандарт (W3C), чем для каждого фреймворка запоминать отличия.
elzvi
Затыки начнутся, когда эти счётчики нужно будет сбросить) но вообще прикольно
isumix Автор
Спасибо! Внутри компонента можно сбросить простым обнулением переменной либо "lift the state up". Снвружи можно передать объект типа
ref.current
в который компонент передаст свой "обнулятор" при создании. Ну или как у всех, подписаться на какой-либо из реактивных стейт менеджеров наmount
.