Первый взгляд на Solid.js

Привет, на связи Antihype JS. Сегодня рассмотрим набирающий популярность frontend фреймворк - Solid. Сравним его с React, рассмотрим плюсы и минусы.

Solid имеет похожее на React API. Он также использует JSX для наших компонентов и построен на концепции однонаправленного потока данных.

Однако, Solid не использует абстракции по типу VDOM для детекта изменений и внесения их в DOM. Фреймворк построен вокруг собственной системы реактивности (сигналы) и компилятора. За счет этого, Solid может работать напрямую с DOM, показывать значительно лучшую производительность и низкое потребление памяти.

Пример простого компонента на Solid:

import { createSignal } from 'solid-js' export const App = () => { const [count, setCount] = createSignal(0) const increment = () => setCount(count() + 1) return ( <div> <p>Antihype JS</p> <button onClick={increment}> {count()} </button> </div> ) }

Ничего не напоминает?

Реактивность

Реактивность в Solid построена на специальных примитивах, так называемых сигналах. Если вы когда-либо сталкивались с MobX, то этот подход будет вам знаком. Для лучшего понимания работы сигналов рекомендуем изучить статью от автора библиотеки.

Сигнал и функция для изменения его значения создаются с помощью createSignal:

import { createSignal } from 'solid-js' const [count, setCount] = createSignal(0)

Обратите внимание, что count является не значением, а функцией.

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

Для отслеживания значения сигнала и создания сайд-эффектов используется createEffect:

import { createEffect } from 'solid-js' createEffect(() => console.log("count:", count()))

Эффект автоматически подписывается на изменения используемых внутри сигналов. Больше никаких массивов зависимостей.

Производный сигнал создается с помощью createMemo:

import { createSignal, createMemo } from 'solid-js' const [firstName] = createSignal('') const [lastName] = createSignal('') const userName = createMemo(() => `${firstName()} ${lastName()}`)

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

Жизненный цикл компонента

import { createSignal, onMount, onCleanup } from 'solid-js' export const Counter = () => { const [count, setCount] = createSignal(0) const increment = () => setCount(count() + 1) onMount(() => console.log('mount')) onCleanup(() => console.log('unmount')) return <button onClick={increment}>{count()}</button> }

В Solid наши компоненты вызываются только один раз, а затем всю работу по обновлению DOM делает система реактивности.

Для подписки на событие монтирования компонента в DOM используется функция onMount, для анмаунта - функция onCleanup.

Больше никаких ререндеров компонентов.

Компилятор JSX

Solid использует свой компилятор JSX шаблонов для работы напрямую с реальным DOM.

После компиляции шаблоны превращаются в оптимизированный JS код для точечного обновления элементов.

Например, наш компонент:

import { createSignal } from 'solid-js' export const App = () => { const [count, setCount] = createSignal(0) const increment = () => setCount(count() + 1) return ( <div> <p>Antihype JS</p> <button onClick={increment}>{count()}</button> </div> ) }

после компиляции превращается в следующий JS код:

const _tmpl$ = /*#__PURE__*/template(`<div><p>Antihype JS</p><button>`); const App = () => { const [count, setCount] = createSignal(0); const increment = () => setCount(count() + 1); return (() => { const _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling; _el$3.$$click = increment; insert(_el$3, count); return _el$; })(); };

Функция создает DOM элементы, привязывает обработчики событий, делает подписку на обновления нужных узлов.

Подписка создается внутри функции insert:

Если запустить наш проект в браузере, то мы увидим, что при изменении значения счетчика обновляется только наша кнопка - parent:

Чтобы ваш код максимально эффективно работал с DOM, библиотека предлагает следующий набор встроенных компонентов для работы с условиями и списками:

<Show />

<Show when={joinedAntihypeJS()} fallback={<button onClick={join}>Join Antihype JS</button>} > <p>Welcome</p> </Show>

when - сигнал предикат

fallback - работает как ветка else, контент будет показан, если сигнал joinedAntihypeJS вернет значение false

<For />

<For each={subscribers()}> {(s, i) => ( <p> {i() + 1}: {s.name} </p> )} </For>

При изменении массива subscribers <For> обновляет или перемещает узлы в DOM, а не создает их заново. Для работы необходимо передать только сигнал массив в пропс each.

Вместо передачи компонента в children <For />, мы передаем колбэк. Первый аргумент - элемент массива, второй - индекс.

Индекс является сигналом по умолчанию. Это сделано для оптимизации при изменении порядка элементов в массиве. Элементы DOM будут поменяны местами и не будут удаляться и создаваться заново.

Bundle size

Воспользуемся сервисом bundlejs для сравнения размера библиотек Solid и React.

react + react-dom: 138kb в минифицированном виде и 44kb в gzip

solid-js: 19kb в минифицированном виде и 7kb в gzip

Performance

Постоянная ссылка на это сравнение на сайте: js-framework-benchmark. Выбрали нативные Solid и React, а также их комбинации с различными стейтменеджерами.

Время рендеринга при манипуляциях со строками таблицы:

Метрики, снятые при старте приложения:

Потребление памяти:

Как видно, производительность Solid в этом бенчмарке сравнима с реализацией на нативном JS.

Экосистема

К сожалению, на текущий момент Solid не может похвастаться такой богатой экосистемой, как React. На сайте библиотеки собраны официальные пакеты и пакеты от сообщества. Уже существуют биндинги для разных стейтменеджеров, роутеры и порты дизайн систем.

Заключение

Использовать Solid в в продакшен проекте или нет - решать исключительно вам. Но наша редакция советует не проходить мимо этого инструмента.

Если вам понравился контент и вы интересуетесь фронтенд-разработкой, то подписывайтесь на наш телеграм-канал.

0
19 комментариев
Написать комментарий...
Сраный Ковбой

1) чем createSignal в solid отличается от useState в react?
2) как вы планируете пробрасывать эти сторы между компонентами?
3) зачем поддувать комментарии пустыми аккаунтами?

Ответить
Развернуть ветку
Antihype JS
Автор

1 - useState это хук
Хуки можно вызвать только внутри компонента, вне мира реакта хуки не работают
При изменении стейта в реакте перерендеривается все дерево ниже, чтобы этого избежать вам надо обмазаться React.memo
В солиде же такой проблемы нет изначально, каждые узлы дома зависят от своих сигналов
2 - props, context, стейтменеджеры (createStore из solid/effector/reatom/storeon/различные tanstack-solid-query/solidapollo)
3 - это наши друзья, а не пустые акки

Ответить
Развернуть ветку
Сраный Ковбой
каждые узлы дома зависят от своих сигналов

Спасибо, понятное объяснение

Ответить
Развернуть ветку
Альберт Базалеев

Все дерево ниже или dom конкретного компонента?

Хм, а в чем тогда смысл сравнения виртуального дерева с изменениями, если идёт ререндеринг?

Ответить
Развернуть ветку
Олег Якунин

Но зачем, если есть божественный Vue?

Ответить
Развернуть ветку
Antihype JS
Автор

не исключено!

Ответить
Развернуть ветку
Sergey Krupskiy

На удивление выглядит не так уж и плохо. Можно даже попробовать, но вряд ли уйдет в продакшн, все таки бизнес любит более устоявшиеся решения. Ну а в эпоху LTE и 5G за размер конечного bundle можно уже не париться))))
Я когда увидел сколько весит приложение банков я порядком так сказать о*уел. Российский Сбер или ВТБ - 530 мегабайт, Карл! Некоторые из этих подопытных умудряются кэша насобирать на 170-800 мегабайт. Боже что вы там кэшируете в таким объемах? Это новый тикток? У меня в каждом тока по одной платежной карте, что там может столько весить?!

Ответить
Развернуть ветку
Antihype JS
Автор

По этой же причине держу на телефоне минимум приложений, больше веб версиями пользуюсь.
Про размер бандла и 5G соглашусь :)

Ответить
Развернуть ветку
Sergey Krupskiy

Откровенно говоря веб версия сбера которая даже не PWA то толком, выглядела настолько плохо, что сайт по доставке пиццы моего заказчика был более дружелюбен и быстр в мобильной версии даже не будучи PWA 😂

Ответить
Развернуть ветку
Олеся К.

Вот фигня с кжшем и правда бесит... Чего там такого может храниться...

Ответить
Развернуть ветку
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
Antihype JS
Автор

Будет реактивный блокчейн

Ответить
Развернуть ветку
Олег Якунин

Какое отношение веб 3 имеет к фронтенду?

Ответить
Развернуть ветку
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
Алексей Кирпиченко

Сколько же человекочасов тратится на изучение этих бесконечных фронтенд фреймворков, мне кажется всё это движется куда-то не туда

Ответить
Развернуть ветку
Antihype JS
Автор

спрос рождает предложение

Ответить
Развернуть ветку
Артём Трифонов

Статья класс, скиньте ссылку Дену Абрамову!!!

Ответить
Развернуть ветку
Antihype JS
Автор

Уже создали PR по замене реакта на солид

Ответить
Развернуть ветку

Комментарий удален модератором

Развернуть ветку
Юрий Ковтун

в общем пока сам не попробуешь ничего не поймешь

Ответить
Развернуть ветку
16 комментариев
Раскрывать всегда