Хабрахабр

[Перевод] Настоящее реактивное программирование в Svelte 3.0

Если вы ещё не знаете ничего про Svelte, пристегнитесь, сейчас мы рванём навстречу революции. Заголовок статьи может показаться немного кричащим, впрочем как и сам фреймворк Svelte и те идеи, что стоят за ним.

Уже существует прекрасное пошаговое интерактивное руководство от команды Svelte, которое погрузит вас в мир реактивного программирования. Учтите, что это не урок по началу работы со Svelte.

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

Хорошо, теперь давайте углубимся в тему!

Но сначала, React

Прежде чем рассказывать, почему я считаю, что Svelte всех порвёт, давайте взглянем на этот недавний твит от человека по имени Dan, и попытаемся понять, что он имел ввиду:

Хм, а почему он тогда называется React?

Я решил использовать его в качестве примера, потому что большинство людей, которые читают эту статью, имели дело с React в тот или иной момент своей жизни. Ещё одна оговорка: эта статья никоим образом не предназначена для критики React. Просто сейчас это лучший пример для противопоставления Svelte.

Чтобы ответить на эти вопросы, позвольте мне упрощённо рассказать о том, как React работает под капотом. Что же имел в виду Dan, и как это повлияло на то, как мы сейчас пишем код?

Этот виртуальный DOM выступает посредником между вашим React-кодом и тем, что браузер отображает в DOM. Когда происходит отрисовка React-приложения, копия DOM помещается в структуру, называемую Virtual DOM.

Затем, при изменении данных (например, после вызова this.setState или useState), React проделывает небольшую работу, чтобы определить, какие части приложения нужно перерисовать.

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

Эта статья намного лучше объясняет этот процесс. Всё происходит очень быстро, потому что обновлять виртуальной DOM намного дешевле, чем реальный, и React обновляет только необходимые кусочки реального DOM.

Если вы не сообщите React, что данные изменились (вызвав this.setState или эквивалентный хук), виртуальный DOM не изменится, и от React не последует никакой реакции (та-дам! Вероятно, вы заметили одну особенность. ).

React полагается на то, что вы самостоятельно отслеживаете данные своего приложения и сообщаете об их изменениях. Вот что имел в виду Dan, когда сказал, что React не полностью реактивен. Это прибавляет вам работы.

Хорошо, теперь про Svelte

И всё это без использования виртуального DOM и с меньшим количеством строк кода, чем понадобилось бы в другом фреймворке или библиотеке. Svelte — это совершенно новый способ создавать веб-приложения невероятно быстрым, эффективным и по-настоящему реактивным способом.

— спросите вы. Это всё, конечно, звучит прекрасно, но чем он отличается от множества других JavaScript библиотек и фреймворков? Я расскажу.

1. Настоящая реактивность

Svelte — это не фреймворк. Svelte — это не библиотека. Скорее, Svelte — это компилятор, который получает ваш код и выдает нативный JavaScript, который напрямую взаимодействует с DOM без необходимости в каком-либо посреднике.

Компилятор? Погоди-погоди, что? И это чертовски хорошая идея, я не могу понять, почему она не была столь очевидной до сих пор. Да  — компилятор. Дальше расскажу, почему я считаю её очень крутой.

Вот цитата из доклада Rich Harris на конференции YGLF 2019:

0 выносит реактивность из API компонента в сам язык. Svelte 3.

Что ж, мы уже знаем, что React (и большинство других фреймворков) требует использовать API, чтобы сообщить ему, об изменении данных (вызов this.setState или useState) и записать их виртуальный DOM. Что это означает?

Необходимость вызова this.setState в React (и иных UI фреймворках и библиотеках) означает, что реактивность вашего приложения теперь привязана к определенному API, без которого оно вообще ничего не будет знать об изменениях в данных.

Svelte использует другой подход.

Вместо обычного запуска кода сверху вниз, он исполняется в топологическом порядке. Его способ исполнения кода был вдохновлён тем, как это сделано в Observable. Посмотрим на фрагмент кода ниже и разберём, что значит — запустить его в топологическом порядке.

1. (() => {
2. let square => number => number * number;
3.
4. let secondNumber = square(firstNumber);
5. let firstNumber = 42;
6.
7. console.log(secondNumber);
8. })();

При исполнении этого кода сверху вниз, будет показана ошибка в строке №4, потому что для secondNumber используется значение firstNumber, которое на этот момент ещё не было инициализировано.

Как так? Если же запустить этот код в топологическом порядке, не будет никаких ошибок. Компилятор не будет запускать этот код сверху вниз, а рассмотрит все переменные в коде и сгенерирует граф зависимостей(то есть, кто от кого зависит изначально).

Предельно упрощённый путь компилятора, при топологическом прохождении нашего примера, выглядит так:

1. Зависит ли новая переменная 'square' от любой другой переменной? - Нет, инициализирую её 2. Зависит ли новая переменная 'secondNumber' от любой другой переменной? - Она зависит от 'square' и 'firstNumber'. Я уже инициализировал 'square', но ещё не инициализировал 'firstNumber', что я сейчас и сделаю. 3. Прекрасно, я инициализировал 'firstNumber'. Теперь я могу инициализировать 'secondNumber' используя 'square' и 'firstNumber' - Есть ли у меня все переменные, требуемые для запуска выражения 'console.log'? - Да, запускаю его.

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

Что ж, именно это происходит в строке №5, поэтому сначала исполняется строка №5, а затем исполнение кода вернётся к строке №4 и пойдёт далее. Когда компилятор доходит до строки №4, он обнаруживает, что у него нет firstNumber, поэтому он приостанавливает дальнейшее выполнение и просматривает весь код, в поисках инициализации этой переменной.

Если кратко: при условии, что выражение A зависит от выражения B, выражение B будет выполняться первым независимо от порядка объявления этих выражений.

Вы можете обозначить любое выражение JavaScript специальной меткой. Итак, как это соотносится с тем, как Svelte реализует свою настоящую реактивность? То есть, всё, что нужно сделать, это добавить метку с именем $ перед выражением foo = barstrict mode этого сделать не получится, если foo не была определена ранее). Это выглядит следующим образом: $: foo = bar.

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

Прямо сейчас мы использовали часть стандартного API самого JavaScript для достижения настоящей реактивности без необходимости возиться со сторонними API типа this.setState. Это и есть реактивность!

Вот как это выглядит на практике:

1. // ванильный js
2. let foo = 10;
3. let bar = foo + 10; // bar теперь равен 20
4. foo = bar // bar всё ещё равен 20 (нет реактивности)
5. bar = foo + 10 // теперь bar равен 30 6. // svelte js
7. let foo = 10;
8. $: bar = foo + 10; // bar равен 20
9. foo = 15 // тут bar станет равным 25, потому что зависит от значения foo

Это делается автоматически. Обратите внимание, что в этом примере нам не нужно было заново пересчитывать bar с новым значением foo, ни напрямую, ни повторным исполнением bar = foo + 10, ни путём вызова метода API, вроде this.setState ().

Svelte уже обо всём знает. То есть, когда вы изменяете foo на 15, bar автоматически обновится на 25, и вам не нужно вызывать никакое API, чтобы сделать это.

Часть скомпилированного Javascript кода, приведенного выше примера, выглядит примерно так:

1. //... 2. function instance($$self, $$props, $$invalidate) {
3. let foo = 10; // bar равен 20 4. $$invalidate('foo', foo = 15) // тут bar станет равным 25, потому что зависит от значения foo 5. let bar; 6. $$self.$$.update = ($$dirty = { foo: 1 }) => {
7. if ($$dirty.foo) { $$invalidate('bar', bar = foo + 10); }
8. }; 9. return { bar };
10. }
11. //...

Не спеша. Не торопитесь читать дальше, изучите этот фрагмент кода.

Это потому, что компилятор анализирует Svelte-код в топологическом порядке, а не построчно сверху вниз. Заметили, что обновление значения foo происходит до того, как bar будет объявлен?

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

Примечание: В строке №4, значение bar не будет обновлено, пока следующая итерация цикла EventLoop не подчистит все хвосты.

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

2. Краткость

Я покажу простой компонент в React и его эквивалент в Svelte, и вы сами убедитесь: Помните, ранее я писал, что Svelte позволяет делать больше, написав при этом меньше строк кода?

17 строк кода против 29

Эти два приложения полностью идентичны по функциональности, но вы можете видеть, сколько кода нам потребовалось написать в React.js —  и даже не просите меня делать это ещё и в Angular .

Я старейший из разработчиков

Например, нам не нужен обработчик событий для обновления значения текстового поля —  достаточно просто сделать привязку. Помимо того, что код Svelte более приятен для глаз, также в нём гораздо проще разобраться, поскольку в нем меньше конструкций.

Какой код был бы для вас менее понятен? Представьте, что вы только начали изучать веб-разработку. Тот, что слева, или тот, который справа?

Я лично потратил часы, пытаясь понять, как работает большой React-компонент, который написал мой коллега. Хотя это может показаться очевидным, но быстро становится понятно, насколько полезнее писать меньше строк кода, когда вы начинаете создавать большие и более сложные приложения.

Я искренне верю, что такой упрощенный API в Svelte позволит намного быстрее читать и понимать код, улучшая нашу общую продуктивность.

3. Производительность

Как насчет производительности? Хорошо, мы увидели, что Svelte по-настоящему реактивный и позволяет делать больше с меньшими усилиями. И насколько удобны приложения, написанные полностью в Svelte?

Одна из причин, почему React настолько мощный, заключается в том, что он использует виртуальный DOM для возможности перерисовки пользовательского интерфейса приложения небольшими частями, устраняя необходимость перерисовывать весь DOM каждый раз, когда что-то меняется (что действительно очень дорого).

Вот почему в React существуют такие методы API, как shouldComponentUpdate, useMemo, React. Однако, недостатком этого подхода является то, что в случае изменения данных компонента React будет повторно перерисовывать не только сам компонент, но и все его дочерние элементы независимо от того, менялись они или нет. PureComponent и т.д.

Это проблема всегда будет иметь место, при использовании виртуального DOM для отрисовки пользовательского интерфейса при изменении состояния приложения.

Что ж, позвольте мне снова привести цитату Rich Harris из его замечательного выступления на YGLF: Svelte не использует виртуальный DOM, но как же тогда он решает проблему перерисовки DOM согласно новым данным состояния?

Это инструменты для организации вашего разума. Фреймворки — это не инструменты организации вашего кода.

Благодаря этой идее, Svelte стал компилятором, а не фреймворком. Эта мысль привела к идее, что фреймворк может быть чем-то, что работает ещё на этапе сборки приложения, устраняя таким образом необходимость для вашего кода иметь посредника в рантайме.

Svelte компилирует ваш код в эффективный низкоуровневый Javascript, который напрямую взаимодействует с DOM. Эта простая идея, также объясняет почему Svelte очень быстрый. Это всё прекрасно, но как Svelte решает проблему перерендеринга всего DOM целиком при изменении данных?

Мы уже ранее обсудили, что в React необходимо вызвать метод API, чтобы сообщить ему, когда данные изменяются. Разница заключается в том, каким образом традиционные фреймворки(например, React) и Svelte узнают, что в состоянии что-то изменилось. В случае Svelte достаточно просто использовать оператор присваивания =.

Это позволяет Svelte перерисовывать только те части DOM, которые так или иначе получают своё значение из foo. Если значение переменой состояния  — скажем, foo  — обновляется при помощи оператора =, Svelte, как мы уже знаем, обновит и все другие переменные, которые зависят от foo.

Но вы можете посмотреть, как сам Rich Harris рассказывает об этом. Я не буду описывать фактическую реализацию этого процесса, потому что эта статья и без того уже достаточно объемная.

В заключение

0 — одна из лучших вещей, которая появилась в области веб-разработки за последнее время. Svelte 3. Концепция Svelte и ее реализация позволяют нам создавать большие приложения, при этом отправляя меньше Javascript-кода в браузер пользователя. Некоторые могут назвать это преувеличением, но я не соглашусь.

Так сможет ли Svelte заменить в ближайшее время React, Angular или любой другой традиционный UI фреймворк? Также он позволяет приложениям быть более производительными, более легковесными и при этом получается код, который легче читать.

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

Подобно тому, как React своим появлением изменил разработку веб-приложений, у Svelte тоже есть потенциал изменить наше представление о фреймворках и возможно, весь процесс мышления при создании приложений.

Счастливого кодинга!

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть