Введение в любой фреймвок начинается с написания одного простого компонента. Чаще всего этим компонентом будет "счетчик нажатий". Это своеобразный "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)


  1. elzvi
    20.05.2025 18:08

    Затыки начнутся, когда эти счётчики нужно будет сбросить) но вообще прикольно


    1. isumix Автор
      20.05.2025 18:08

      Спасибо! Внутри компонента можно сбросить простым обнулением переменной либо "lift the state up". Снвружи можно передать объект типа ref.current в который компонент передаст свой "обнулятор" при создании. Ну или как у всех, подписаться на какой-либо из реактивных стейт менеджеров на mount.


  1. nihil-pro
    20.05.2025 18:08

    В терминах React это называется "managed input" компонент. Его попробовать можно тут в альтернативном стиле (не JSX).

    В вашем инпуте каретка скачет если напечатать что-то в середине текста.


    1. isumix Автор
      20.05.2025 18:08

      Да, есть такое, для наглядности этот компонент очень простой, в Реакт будет такое же поведение.


      1. nihil-pro
        20.05.2025 18:08

        Нет, в реакте такого поведения не будет.


        1. isumix Автор
          20.05.2025 18:08

          Вы уверены? Это стандартное поведение элемента <input /> при присвоении input.value = x.


          1. nihil-pro
            20.05.2025 18:08

            Вы уверены?

            Разумеется.


            1. isumix Автор
              20.05.2025 18:08

              А что же вы опустили toUpperCase? С ним как раз будет точно такое же поведение.

              Кстати странно, я думал что это по причине присвоения, а тут что-то другое, также цифры работают корректно, надо будет покопаться.


              1. green_fenix
                20.05.2025 18:08

                Если я правильно понимаю, скачет когда .value дом элемента инпута меняется на не идентичную текущей строку программно.

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

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


                1. isumix Автор
                  20.05.2025 18:08

                  Да, видимо так и работает. Возможно если сохранять позицию курсора и восстанавливать ее после присвоения, то проблема решится.


                  1. nin-jin
                    20.05.2025 18:08

                    Именно так и работает $mol_string, но ни в коем случае не подглядывайте в $mol, продолжайте набивать шишки.


                    1. isumix Автор
                      20.05.2025 18:08

                      Философия Фьзора - не трогать дефолтное поведение. Лучше один раз изучить стандарт (W3C), чем для каждого фреймворка запоминать отличия.


  1. green_fenix
    20.05.2025 18:08

    Ждем nin-jin с комментарием про то что $mol лучше