Заголовок статьи может показаться немного кричащим, впрочем как и сам фреймворк Svelte и те идеи, что стоят за ним. Если вы ещё не знаете ничего про Svelte, пристегнитесь, сейчас мы рванём навстречу революции.
Учтите, что это не урок по началу работы со Svelte. Уже существует прекрасное пошаговое интерактивное руководство от команды Svelte, которое погрузит вас в мир реактивного программирования.
Оговорка: я не рок-звезда в программировании и не знаю всего на свете. Я просто с энтузиазмом отношусь к новым веяниям, которые случаются ежедневно, и мне нравится, когда я могу, говорить о них — так появилась и эта статья. Отнеситесь к ней критически и обязательно дайте знать, если я написал что-нибудь нелепое.
Хорошо, теперь давайте углубимся в тему!
Но сначала, React
Прежде чем рассказывать, почему я считаю, что Svelte всех порвёт, давайте взглянем на этот недавний твит от человека по имени Dan, и попытаемся понять, что он имел ввиду:
Хм, а почему он тогда называется React?
Ещё одна оговорка: эта статья никоим образом не предназначена для критики React. Я решил использовать его в качестве примера, потому что большинство людей, которые читают эту статью, имели дело с React в тот или иной момент своей жизни. Просто сейчас это лучший пример для противопоставления Svelte.
Что же имел в виду Dan, и как это повлияло на то, как мы сейчас пишем код? Чтобы ответить на эти вопросы, позвольте мне упрощённо рассказать о том, как React работает под капотом.
Когда происходит отрисовка React-приложения, копия DOM помещается в структуру, называемую Virtual DOM. Этот виртуальный DOM выступает посредником между вашим React-кодом и тем, что браузер отображает в DOM.
Затем, при изменении данных (например, после вызова this.setState
или useState
), React проделывает небольшую работу, чтобы определить, какие части приложения нужно перерисовать.
Он сравнивает виртуальный DOM с реальным, чтобы определить, что изменилось после обновления данных. Затем перерисовывает только те части DOM, которые имеют отличия в виртуальном DOM, устраняя необходимость каждый раз при любом изменении, перерисовывать весь DOM целиком.
Всё происходит очень быстро, потому что обновлять виртуальной DOM намного дешевле, чем реальный, и React обновляет только необходимые кусочки реального DOM. Эта статья намного лучше объясняет этот процесс.
Вероятно, вы заметили одну особенность. Если вы не сообщите React, что данные изменились (вызвав this.setState
или эквивалентный хук), виртуальный DOM не изменится, и от React не последует никакой реакции (та-дам! ).
Вот что имел в виду Dan, когда сказал, что React не полностью реактивен. React полагается на то, что вы самостоятельно отслеживаете данные своего приложения и сообщаете об их изменениях. Это прибавляет вам работы.
Хорошо, теперь про Svelte
Svelte — это совершенно новый способ создавать веб-приложения невероятно быстрым, эффективным и по-настоящему реактивным способом. И всё это без использования виртуального DOM и с меньшим количеством строк кода, чем понадобилось бы в другом фреймворке или библиотеке.
Это всё, конечно, звучит прекрасно, но чем он отличается от множества других JavaScript библиотек и фреймворков? — спросите вы. Я расскажу.
1. Настоящая реактивность
Svelte — это не библиотека. Svelte — это не фреймворк. Скорее, Svelte — это компилятор, который получает ваш код и выдает нативный JavaScript, который напрямую взаимодействует с DOM без необходимости в каком-либо посреднике.
Погоди-погоди, что? Компилятор? Да ?— компилятор. И это чертовски хорошая идея, я не могу понять, почему она не была столь очевидной до сих пор. Дальше расскажу, почему я считаю её очень крутой.
Вот цитата из доклада Rich Harris на конференции YGLF 2019:
Svelte 3.0 выносит реактивность из API компонента в сам язык.
Что это означает? Что ж, мы уже знаем, что 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'?
- Да, запускаю его.
На первый взгляд кажется, что код отрабатывает сверху вниз, но если присмотреться, можно заметить, что это не так.
Когда компилятор доходит до строки №4, он обнаруживает, что у него нет firstNumber
, поэтому он приостанавливает дальнейшее выполнение и просматривает весь код, в поисках инициализации этой переменной. Что ж, именно это происходит в строке №5, поэтому сначала исполняется строка №5, а затем исполнение кода вернётся к строке №4 и пойдёт далее.
Если кратко: при условии, что выражениеA
зависит от выраженияB
, выражениеB
будет выполняться первым независимо от порядка объявления этих выражений.
Итак, как это соотносится с тем, как Svelte реализует свою настоящую реактивность? Вы можете обозначить любое выражение JavaScript специальной меткой. Это выглядит следующим образом: $: foo = bar
. То есть, всё, что нужно сделать, это добавить метку с именем $
перед выражением foo = bar
(в strict mode этого сделать не получится, если foo
не была определена ранее).
Так что, когда 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 ({bar = foo + 10})
. Это делается автоматически.
То есть, когда вы изменяете foo
на 15
, bar
автоматически обновится на 25
, и вам не нужно вызывать никакое API, чтобы сделать это. Svelte уже обо всём знает.
Часть скомпилированного 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. //...
Не торопитесь читать дальше, изучите этот фрагмент кода. Не спеша.
Заметили, что обновление значения foo
происходит до того, как bar
будет объявлен? Это потому, что компилятор анализирует Svelte-код в топологическом порядке, а не построчно сверху вниз.
Svelte самостоятельно реагирует на изменения данных. Вам не нужно отслеживать, что именно меняется и когда — это происходит само собой.
Примечание: В строке №4, значение bar
не будет обновлено, пока следующая итерация цикла EventLoop не подчистит все хвосты.
Таким образом, нам больше не нужно заботиться о ручном обновлении состояния при изменении данных. Вы можете весь день думать над логикой приложения, пока Svelte занимается согласованием UI с актуальным стейтом.
2. Краткость
Помните, ранее я писал, что Svelte позволяет делать больше, написав при этом меньше строк кода? Я покажу простой компонент в React и его эквивалент в Svelte, и вы сами убедитесь:
17 строк кода против 29
Эти два приложения полностью идентичны по функциональности, но вы можете видеть, сколько кода нам потребовалось написать в React.js —? и даже не просите меня делать это ещё и в Angular .
Я старейший из разработчиков
Помимо того, что код Svelte более приятен для глаз, также в нём гораздо проще разобраться, поскольку в нем меньше конструкций. Например, нам не нужен обработчик событий для обновления значения текстового поля —? достаточно просто сделать привязку.
Представьте, что вы только начали изучать веб-разработку. Какой код был бы для вас менее понятен? Тот, что слева, или тот, который справа?
Хотя это может показаться очевидным, но быстро становится понятно, насколько полезнее писать меньше строк кода, когда вы начинаете создавать большие и более сложные приложения. Я лично потратил часы, пытаясь понять, как работает большой React-компонент, который написал мой коллега.
Я искренне верю, что такой упрощенный API в Svelte позволит намного быстрее читать и понимать код, улучшая нашу общую продуктивность.
3. Производительность
Хорошо, мы увидели, что Svelte по-настоящему реактивный и позволяет делать больше с меньшими усилиями. Как насчет производительности? И насколько удобны приложения, написанные полностью в Svelte?
Одна из причин, почему React настолько мощный, заключается в том, что он использует виртуальный DOM для возможности перерисовки пользовательского интерфейса приложения небольшими частями, устраняя необходимость перерисовывать весь DOM каждый раз, когда что-то меняется (что действительно очень дорого).
Однако, недостатком этого подхода является то, что в случае изменения данных компонента React будет повторно перерисовывать не только сам компонент, но и все его дочерние элементы независимо от того, менялись они или нет. Вот почему в React существуют такие методы API, как shouldComponentUpdate
, useMemo
, React.PureComponent
и т.д.
Это проблема всегда будет иметь место при использовании виртуального DOM для отрисовки пользовательского интерфейса при изменении состояния приложения.
Svelte не использует виртуальный DOM, но как же тогда он решает проблему перерисовки DOM согласно новым данным состояния? Что ж, позвольте мне снова привести цитату Rich Harris из его замечательного выступления на YGLF:
Фреймворки — это не инструменты организации вашего кода. Это инструменты для организации вашего разума.
Эта мысль привела к идее, что фреймворк может быть чем-то, что работает ещё на этапе сборки приложения, устраняя таким образом необходимость для вашего кода иметь посредника в рантайме. Благодаря этой идее, Svelte стал компилятором, а не фреймворком.
Эта простая идея, также объясняет почему Svelte очень быстрый. Svelte компилирует ваш код в эффективный низкоуровневый Javascript, который напрямую взаимодействует с DOM. Это всё прекрасно, но как Svelte решает проблему перерендеринга всего DOM целиком при изменении данных?
Разница заключается в том, каким образом традиционные фреймворки(например, React) и Svelte узнают, что в состоянии что-то изменилось. Мы уже ранее обсудили, что в React необходимо вызвать метод API, чтобы сообщить ему, когда данные изменяются. В случае Svelte достаточно просто использовать оператор присваивания =
.
Если значение переменой состояния ?— скажем, foo
?— обновляется при помощи оператора =
, Svelte, как мы уже знаем, обновит и все другие переменные, которые зависят от foo
. Это позволяет Svelte перерисовывать только те части DOM, которые так или иначе получают своё значение из foo
.
Я не буду описывать фактическую реализацию этого процесса, потому что эта статья и без того уже достаточно объемная. Но вы можете посмотреть, как сам Rich Harris рассказывает об этом.
В заключение
Svelte 3.0 — одна из лучших вещей, которая появилась в области веб-разработки за последнее время. Некоторые могут назвать это преувеличением, но я не соглашусь. Концепция Svelte и ее реализация позволяют нам создавать большие приложения, при этом отправляя меньше Javascript-кода в браузер пользователя.
Также он позволяет приложениям быть более производительными, более легковесными и при этом получается код, который легче читать. Так сможет ли Svelte заменить в ближайшее время React, Angular или любой другой традиционный UI фреймворк?
Пока я отвечу — нет. Svelte слишком молод по сравнению с ними, поэтому ему нужно время, чтобы вырасти, повзрослеть и разобраться в некоторых причудах, о которых мы пока даже не подозреваем.
Подобно тому, как React своим появлением изменил разработку веб-приложений, у Svelte тоже есть потенциал изменить наше представление о фреймворках и возможно, весь процесс мышления при создании приложений.
Счастливого кодинга!
Комментарии (100)
Mox
25.05.2019 22:51Сравнение размера кода по количеству строк особенно забавно
— В React версию добавили div, import и отформатировали по атрибуту на каждой строке. Данные можно хранить как через хуки, так и в state. В чем разница по количеству кода — ну в React надо хендлер явно указать, а там прям какая-то магия связывания. По моему опыту — эти handler — очень малая часть кода.AlexxNB Автор
25.05.2019 23:10+1В данной статье эти пара абзацев про разное количество строк кода отнюдь не главная мысль. Похоливарить на эту тему можно в предыдущей статье авторства Rich Harris.
justboris
25.05.2019 23:54-1Вот именно, что в той статье в комментариях были разумные замечания на эту тему. И здесь наступают на те же грабли.
А если опустить часть статьи про компактность кода, то аргументация статьи в целом проседает и уже не выглядит такой убедительной.
AlexxNB Автор
26.05.2019 00:19+1Аргументация про реактивное программирование проседает?
Все ж про более понятный код компонентов Svelte, тут в достаточно утрированном виде, для обывателей рассказано. Можно опять начать спорить, но вся конкретика уже была в упомянутой выше статье и там каждый остался при своём мнении.
justboris
26.05.2019 02:01Я к тому, что если у автора проседает аргументация в одном месте, то и статью целиком сложно воспринимать серьезно. Видно, что автор топит за свою любимую технологию, а значит и обзор получается предвзятым.
AlexxNB Автор
26.05.2019 09:20Я не считаю, что она проседает — поверхностна, да. Но цели углубляться в эту тему в этой статье не было. Для меня структура компонентов Svelte3 сильно лаконичнее и понятнее, чем у любого другого фреймворка. Вот интересно, если бы отписался кто-нибудь, кто не трогал ещё ни Svelte, ни другие топовые фреймворки, со времён JQuery. Какой код ему понятнее?
Если хотите прямо фактов — напишите свой канонически верный вариант на React и посчитаем символы, а не строки. Всё равно букв у вас будет больше, как ни старайтесь. Но, что это докажет вам или мне?
Прошлые статьи автора были в том числе и про React. Теперь попробовал Svelte. Так что автор не голословен, что-то для себя нашёл в Svelte, раз написал такую длинную статью.
bano-notit
26.05.2019 11:58кто не трогал ещё ни Svelte, ни другие топовые фреймворки, со времён JQuery. Какой код ему понятнее?
Человеку будет понятен императивный подход без всей этой реактивности. Он ведь ничего кроме jq не видал)
0xd34df00d
26.05.2019 16:05+1Я jQuery тоже не видал, да и вообще вебдевом не занимаюсь, а пишу на хаскеле и сиплюсплюс. Так вот, код со Svelte, естественно, понятнее. И приятнее, меньше бойлерплейта.
Другое дело, что я не могу представить, насколько хорошо этот подход скейлится, и не вылезут ли подводные камни.
DarthVictor
26.05.2019 08:28+1Реактивное программирование в Svelte и без этой статью вызывает вопросы. Вроде и понятно почему так (в JS нельзя узнать, что метод мутирует объект). Но все равно неприятно и можно пропустить. В проде народ что-то типа ImmutableJS использует?
AlexxNB Автор
26.05.2019 11:30Насколько я видел, кто работает со Svelte предпочитают решать эту проблему явным присваиванием. Например
array.push(3)
пишут какarray = [...array,3]
. Но я не вижу причин, почему бы не использовать ImmutableJS для этого, если кому-то так удобнее.staticlab
26.05.2019 12:28Потому что в Svelte обновление тригеррится именно по операции присвоения в svelte-компоненте, а присвоение внутри ImmutableJS останется незамеченным?
AlexxNB Автор
26.05.2019 12:46Так ведь суть ImmutableJS — что он предлагает неизменяемые объекты? Т.е. все преобразования выдают новый объект, который уже можно присвоить через
=
.
const { Map } = require('immutable'); const map1 = Map({ a: 1, b: 2, c: 3 }); const map2 = map1.set('b', 50); map1.get('b') + " vs. " + map2.get('b'); // 2 vs. 50
staticlab
26.05.2019 12:59Да, здесь вы правы. Но всё равно стрёмно, что это только в пределах *.svelte сработает, а если отрефакторить и вынести в отдельный файл, то сломается.
MaM
26.05.2019 03:34В js есть прокси, это убирает проблему setState, разработчики не хотели их вводить из за обратной совместимости, почему в Babel нет я уже и не найду. Замена кода на комп лятор в js похожа на золотой унитаз и сводит на снова в дискуссию компиляторы Vs джиты, где как известно победит aot, и окажет мы, что мы говорим о профилях кода. Ну и к вопросу dom, тоже не совсем понятно надо ли, я думаю, что нет. Компиляторы или интерпретатор должен решать проблемы кода а не тулза. Так что выбирая, лучше улучшать последний а не пытаться развернуть поудобней граф исполнения, а в крайности вообще лучше все в васм перевести да и делов
andreyiq
26.05.2019 06:46Почему Svelte постоянно сравнению с React? Я когда вижу в статьях код написанный на Svelte, не сразу отличаю его от Angular и Vue.
naumovarthur
26.05.2019 09:28-1Автор специально игнорирует Vue? Он как раз решает эти проблемы React. И он более зрелый и развитый фреймворк.
AlexxNB Автор
26.05.2019 09:35Автор не ставил целью сравнить все фреймворки в очередной раз. Автор пытался объяснить механизм работы реактивности в Svelte — что для неё не нужны какие-либо чужеродные конструкции, а только то, что есть в самом Javascript.
К слову, я бы с удовольствием почитал статью, где сравнивался бы не Svelte с Vue, а Vue со Svelte, например.
staticlab
26.05.2019 12:33+2Как раз требуются: семантика svelte-кода отличается от семантики js-кода. Код обратно несовместим, то есть по сути у нас совсем другой язык. В частности, отличается интерпретация операции присвоения, использования меток и семантика переменных, начинающихся с $.
AlexxNB Автор
26.05.2019 13:08Вы совершенно правы. Можно назвать это Sveltescrypt. С
$
это просто сахар, который легко запомнить и использовать. Но синтаксис сам по себе — всё еще валидный JS, и шаблоны проектирования остаются те же. Хотите переменную стейта — просто пишитеlet var
— как вы делаете для создания обычных переменных Javascript, а не используете "чужеродную конструкцию" для этого.staticlab
26.05.2019 13:32+1Нет, не валидный:
import { value } from './store'; $value = 123;
Это не валидный JS. С точки зрения JS переменной $value в данном контексте не существует. В строгом режиме будет ReferenceError при обращении к необъявленной переменной $value.
AlexxNB Автор
26.05.2019 19:17JS не валидный, синтаксис валидный.
staticlab
26.05.2019 19:36+1Ну а какой вообще смысл в валидном синтаксисе, если его семантика всё равно не соответствует общепринятой? Сделали бы тогда уже особый синтаксис, например,
<-
вместо присвоения и т.п.AlexxNB Автор
26.05.2019 19:58-2Скорее всего это сделано, чтобы имеющийся туллинг для Javascript был готов из коробки, например подсветка синтаксиса на Хабре.
Кроме того — реактивные выражения в Svelte — это не обязательно только присваивание. Можно сделать реактивным любое выражение и даже целый блок:
let varA = 0; $: { console.log(varA); console.log(varA*2); } // В консоли будут показываться новые значения, всякий раз, когда изменится varA
staticlab
26.05.2019 20:38+5Ок, вернёмся к нашим баранам. Тулза для JS, например ESLint или IDE, скажет, что $value не определена, а value не используется. Семантика-то другая, да и синтаксически $ теперь уже не часть имени, а оператор. Что толку тогда от такого тулинга?
Более того, в том же Vue, если не ошибаюсь, долгое время были проблемы с тулингом из-за vue-файлов, хотя JS там настоящий. Отсюда возникает и второй вопрос: почему разработчики гуляют по тем же самым граблям?
mayorovp
26.05.2019 20:54Отсюда возникает и второй вопрос: почему разработчики гуляют по тем же самым граблям?
Вот это как раз понятно: со встроенным в язык тулингом ничего хитрее Реакта не придумать, а Реакт уже придуман.
staticlab
26.05.2019 20:57Не очень вас понял. Какой встроенный в язык тулинг вы имеете в виду? Дебаггер браузера?
AlexxNB Автор
26.05.2019 21:16-3Ок, вернёмся к нашим баранам.
Ну, не весь имеющийся туллинг же, а по синтаксису. Поэтому для Svelte с первых версий сразу была годная подсветка синтаксиса и притир в любой IDE(особенно, когда компоненты были в *.html файлах ещё). Иначе, в комментариях к статьям про Svelte, только и делали бы, что жаловались на невозможность писать на Svelte где бы то ни было. Сейчас уже экосистема потихоньку обрастает. Есть плагин и для того же ESLint.
Смысл в том, что Svelte — это языки, которые вы уже знаете. HTML, CSS и JS. Т.е. если вы знакомы с этими языками — вы уже знаете 90% Svelte. В каждый из этих языков добавлено немного магии — на изучение которой хватит 15 минут чтения учебника на сайте.
faiwer
27.05.2019 21:15В принципе эта проблема решаема. К примеру для jsx-control-statements есть плагин для eslint. Правда я не уверен, что кто-то делал это для Svelte.
staticlab
27.05.2019 22:07Конечно решаема, но ведь всё якобы задумывалось, согласно цитате выше, «чтобы имеющийся тулинг для Javascript был готов из коробки». А в результате всё равно получается, что нужны специфичные плагины.
faiwer
27.05.2019 22:58Хе-хе, ну есть же динозавры, у которых не линтеров, ни подсветки синтаксиса, ни прочих удобств. У них тулинг "всегда готов" как пионер.
А если серьёзно, то всегда остаётся какая-нибудь Jet Brains (ну или аналог) со своими парсерами, анализаторами и прочим. Проблема тулинга полноценно решается только для очень крупных игроков (скажем поддержка JSX есть уже в каждой кофемолке). А для несколько менее популярного Vue только пол пути пройдено.
Вот из недавнего — prettier не умеет в новый pipe-operator в smart виде. Очень удобная штука, но пришлось от неё отказаться :(
markmariner
28.05.2019 09:48Вы путаете, там написано было бы:
$: value = 123staticlab
28.05.2019 09:56+2Нет, не путаю, это обращение к стору, а не просто к локальной переменной: https://svelte.dev/tutorial/auto-subscriptions
YemSalat
28.05.2019 13:13-1Ну вот у вас в первом скрипте уже не валидный JS:
let secondNumber = square(firstNumber); let firstNumber = 42;
Довольно спорное решение менять сам язык ради фреймворка.
extempl
26.05.2019 09:34Производительность
Я не буду описывать фактическую реализацию этого процесса, потому что эта статья и без того уже достаточно объемная.Интересный факт — это самая важная часть, которую можно противопоставлять существующим фреймворкам типа Реакта. Всё остальное (включая синтаксис и кол-во строк кода) — сильно вторично.
DmitryKoterov
26.05.2019 09:39+3Что с TypeScript? Не сыграет ли со Svetle злую шутку то, что Svetle — компилятор? Ведь писать на чистом JS в 2019 году несколько некомфортно, а TypeScript уже чертовски хорош и улучшается семимильными шагами.
DmitryKoterov
26.05.2019 09:42+1… я к тому, что в React с TypeScript все замечательно — строгая типизация, вывод типов, GraphQL, поддержка в IDE и т.д.
MadLord
26.05.2019 12:30+3> Если вы не сообщите React, что данные изменились (вызвав this.setState или эквивалентный хук), виртуальный DOM не изменится, и от React не последует никакой реакции (та-дам! ).
Тоже самое будет, если не указать $: в Svelte.
> Представьте, что вы только начали изучать веб-разработку. Какой код был бы для вас менее понятен? Тот, что слева, или тот, который справа?
А если не только начали и работаете в команде? Какой код будет более понятен без чтения доп мануалов?
Ну и отсутствие typescript, конечно, огромный минус…AlexxNB Автор
26.05.2019 13:25Тоже самое будет, если не указать $: в Svelte.
Не совсем то же самое.
$:
просто назначает следующее за ним выражение быть реактивным — по сути это destiny operator. Кроме него есть ещё переменные стейтаlet somevar;
. Тут вообще ничего не надо делать, чтобы при изменении через присваивание их значения отображались на вьюшке. Аthis.setState
вы обязаны вызывать каждый раз, когда собираетесь изменить данные.
А если не только начали и работаете в команде? Какой код будет более понятен без чтения доп мануалов?
А если команда работает на JQuery, а не на React?
MadLord
27.05.2019 07:40А this.setState вы обязаны вызывать каждый раз, когда собираетесь изменить данные.
Тоже самое делает Svelte у себя внутри, только это уже никак не контролируется разработчиком. В React можно четко описывать, когда нужно делать обновление состояния (например, не всегда изменение переменной должно приводить к изменению DOM).
Хотя соглашусь, для простых случаев подход Svelte довольно удобен.
А если команда работает на JQuery, а не на React?
Эммм… и в чем проблема?...JQuery — просто библиотека… подключить ее к React не проблема…mayorovp
27.05.2019 09:12+1Да нет, таки проблема. JQuery — это "просто библиотека" для манипуляций DOM. React — тоже библиотека для манипуляций DOM. Когда две разные библиотеки начинают манипулировать DOM — результат начинает зависеть от мелких деталей их реализации.
serf
26.05.2019 12:49В Svelte пропагандируется one-file-component. То есть стили, код и шаблон все в оном файле. При таком подходе сразу отрубается множество полезных инструментов статического анализа кода, форматирования и тд.
Tangeman
27.05.2019 01:50-1Чем же лучше react/angular/flutter & co, где шаблон хорошо разбавлен кодом (или наоборот)? Уж лучше один файл но в нём отдельно шаблон и код, чём php-style 15-ти летней давности, пусть и с улучшенным синтаксисом.
staticlab
27.05.2019 09:11В angular код отделён от шаблона, и они в принципе в разных файлах лежат.
Tangeman
27.05.2019 14:07Прошу прощения. Видимо, мне просто попался такой фрагмент кода на angular — со вставками markup, и я экстраполировал на весь фреймворк.
serf
28.05.2019 14:24В angular действительно шаблоны можно инлайнить в код компонента как строку, но это опциональная возможность. Как правило шаблоны там в отдельных файла что хорошо. А вот Svelte насколько я понимаю не позволяет использвать 3-files-component модель разработки которая по моему мнению предпочтительна.
serf
28.05.2019 14:30php-style 15-ти летней давности
JSX пропагандируемый реактом и есть 15-ти летней давности спагетти-код-стайл, видимо потому что в FB изначально было много PHP кодеров и дос сих пор таковые диктуют правила игры имея огромные рычаги маркетингового влияния.
Vasily_T
26.05.2019 17:32+1Агрессивный маркетинг, но по сути это как картинка про «еще один стандарт»,
только вот нового ничего Svelte ни привносит, более того несколько искажает то что есть в попытке выдать за новое
Tangeman
27.05.2019 01:43У Svelte, равно как и множества других реактивных фреймворков, есть большой недостаток — это динамическое (пере)создание элементов при обновлении их содержимого. К примеру, даже сравнительно статический фрагмент из банального <h1>Hello {name}</h1> превращается в такой код:
c() { h1 = element("h1"); t0 = text("Hello "); t1 = text(name); t2 = text("!"); }, m(target, anchor) { insert(target, h1, anchor); append(h1, t0); append(h1, t1); append(h1, t2); },
Вопрос — зачем? Если элемент статический и долгоживущий — почему на этапе компиляции не создать его один раз и потом не менять только содержимое, храня только ссылку на единожды созданный элемент?
Если постоянно динамически это делать, это серьезный удар по производительности (если, конечно, это не контент который меняется раз в сто лет), не говоря уже о размере кода который нужен для создания всех элементов (если их много).
Собственно сам browser намного быстрее всё отрендерит и построит из html, чем из js-кода, изменения только контента намного быстрее чем полное пересоздание элементов.
Пока же, увы, тенденция такова что темплейты в html просто тупо транслируются в js, фактически убивая всё то хорошее ради чего был создан собственно html (попробуйте ради интереса сделать в свелте <div>static text<div> — получите тоже кусок js).
Да, оно работает, в большинстве случаев достаточно быстро чтобы пользователи не видели разницы, но это всё равно неэффективно — как по использованию процессора, так и памяти.mayorovp
27.05.2019 09:19+1Вопрос — зачем? Если элемент статический и долгоживущий — почему на этапе компиляции не создать его один раз и потом не менять только содержимое, храня только ссылку на единожды созданный элемент?
m — это операция mount, она выполняется только 1 раз. Смотрите функцию update, там всё именно так как вы хотели!
Tangeman
27.05.2019 13:34Если переменная name изменяется, элемент будет пересоздаваться — весь, снова и снова — в этом я и вижу проблему.
mayorovp
27.05.2019 13:45Нет, не будет:
p(changed, ctx) { if (changed.name) { set_data(t1, ctx.name); } },
Если вы сомневаетесь по поводу set_data — вот её реализация:
export function set_data(text, data) { data = '' + data; if (text.data !== data) text.data = data; }
staticlab
27.05.2019 13:50Нет, и в Свелте, и в Реакте, и в Ангуляре будет обновляться только та часть DOM-дерева, которая реально изменилась. Если изменилась text node, то тоже только она изменится, а сам элемент останется нетронутым. Просто механизм, которым обеспечивается минимально необходимое изменение дерева, разный.
Tangeman
27.05.2019 14:01Ok, хорошо — но остается всё же массивный объем кода для создания даже статики. Банальный
создается посредством js:<div>static text</div>
т.е. для сравнительного большого темплейта будет куча кода. А куча кода это куча кода — даже если он выполняется один раз.div = element("div"); div.textContent = "static text";
Зачем такой огород если можно просто скормить браузеру собственно html с нужными вставками?justboris
27.05.2019 14:53Банальный
<div>static text</div>
на самом деле не так уж прост. Браузер создаст все те же DOM-элементы внутри, что и мы сами руками, но при этом у нас не будет возможности переиспользовать div и обновить только textContent.
Еще есть вот такой доклад, например, в котором рассказывается об этом больше: https://habr.com/en/company/oleg-bunin/blog/310868/
AlexxNB Автор
27.05.2019 13:55Совсем нет. Существует ссылка на текстовую ноду — в нашем случае
t1
. Эта нода и обновляется, нет необходимости заново рисовать весь компонент:
... if (changed.name) { set_data(t1, ctx.name); } ...
PS: долго писал… уже объяснили
AlexxNB Автор
27.05.2019 09:24+1Я рад, что кто-то смотрит на скомпилированный код, прежде чем теоретизировать по поводу работы Svelte. Тот фрагмент кода, что вы указали, выполняется один раз при инициализации экземпляра данного компонента. Далее работа ведётся уже точечно с нужными нодами, обычно с текстовыми(напомню, что сам текст в элементе — это отдельная нода). Эти функции просто сокращения нативных функций Javascript для прямой работы с DOM:
append(h1,t0)
это не что иное, какh1.appendChild(t0)
.
Если использовать в лобnode.innerHTML('...')
, то потом всё равно придется распарсивать DOM, чтобы получить ссылки на ноды(иначе как работать фреймворку?). Поэтому используется методdocument.createDocumentFragment()
— тоже достаточно производительный и все ссылки имеются сразу.
Ещё можно использовать SSR с гидратацией. Это Svelte тоже умеет.Tangeman
27.05.2019 13:51чтобы получить ссылки на ноды(иначе как работать фреймворку?)
getElementById() же, нет? Темплейт в слегка переработанном виде отдаётся браузеру, где надо расставляются id, по этим id один раз (при инициализации) берутся объекты, а дальше меняется только то что должно, без репарсинга. Компилятор ведь всё равно разбирает темплейт, так что создать эффективный код не должно быть проблемой.
Для любых контейнеров где внутри нет HTML достаточно будет менять innerText, а не innerHTML, можно даже делать это более эффективно, если «добавить воды»:
<div>Hello, {name}!</div>
транслируется в что-то типа:
<div>Hello, <span id="var-name" ></span></div>
после чего меняется только innerText в #var-name (адрес которого берется один раз).
И в любом случае, браузер гораздо быстрее и эффективнее разберет фрагмент html чем вы «вручную» будете его создавать в js посредством манипуляции DOM. Проведите эксперимент — создайте динамически (js) таблицу с большим количеством элементов, а потом её же но уже через установку innerHTML — разница будет ощутима.mayorovp
27.05.2019 13:58+2Это хороший способ когда в разметке много статического содержимого. А вот если там, как и должно быть в компоненте, преимущественно динамика — выигрыш будет уже не таким заметным, если вообще будет.
Tangeman
27.05.2019 14:54Это зависит от приложения, часто бывает так что изменяемый контент составляет сравнительно небольшую часть по отношению к статике. Яркий пример — некоторые dashboards, где собственно статика (стили, маркап) занимают 70-80%.
mayorovp
27.05.2019 15:03Ну, стили-то или отдельным файлом загружаются, или одной текстовой строкой. Их как раз можно не считать.
AlexxNB Автор
27.05.2019 15:49. Проведите эксперимент — создайте динамически (js) таблицу с большим количеством элементов, а потом её же но уже через установку innerHTML — разница будет ощутима.
Не поленился — провёл. Вставка 1000
Div
. Мои результаты: innerHTML — 4ms, fragment — 9ms. Вы правы — разница более 2 раз не в пользу fragment. Но есть пара моментов:
- Бенчмарк не учитывает последующую работу с getElementById() в случае innerHTML
- После фрагмента у нас уже есть ссылки на все ноды в документе и не надо выдумывать ничего с getElementById() — что тоже заняло бы какое-то время.
- 9 мс это реально очень-очень-очень мало.
Tangeman
27.05.2019 16:28getElementById() можно вызвать только один раз — потом переиспользовать результат (элемент-то статичен).
И да, 9 ms это конечно мало — но и элементов всего 1000. А если их 1000 и в каждом ещё по два десятка вложенных (если это табличка с расчётом на модификацию значений пользователем)?
А теперь представьте что это выполняется не на топовом железе а на средненьком мобильном или просто нетбуке — эти 9 ms легко могут превратиться во все 100 ms.
Мне кажется, если бы девелоперов заставить тестировать продукцию на слабом железе с ограниченными ресурсами, то ситуация с оптимизацией резко бы изменилась, но сейчас, увы, тенденция — наращивание мощности процессоров и памяти…AlexxNB Автор
27.05.2019 16:36Тестируйте, пожалуйста, на всех моих устройствах результаты меняются в пределах погрешности.
staticlab
27.05.2019 16:45А если их 1000 и в каждом ещё по два десятка вложенных (если это табличка с расчётом на модификацию значений пользователем)?
- Зачем сразу явно выводить разметку редакторов ячеек? Пользователь всё равно не может редактировать несколько одновременно.
- Вряд ли пользователь может просмотреть сразу 1000 строк. Стоит подумать об оптимизации вывода во вьюпорт.
Tangeman
27.05.2019 17:36-2Разметка нужна не только для редактирования, если это сложная таблица с несколькими колонками, вероятно с иконками, разными шрифтами или цветами — вот вам уже минимум десяток элементов на строку.
А вот насчёт 1000 строк — это зависит. К примеру, когда я просматриваю события системы мониторинга или журналы — то бывает и по 10 тыс. строк, это гораздо удобней чем листать по 100. Опыт показывает что отдача браузеру маркапа (созданного либо на сервере либо в самом js) существенно ускоряет рендеринг.
Я думаю если появится такой «вумный» компилятор-фреймворк который всё оптимизирует примерно так как я описываю (код только там где невозможно обойтись html) — он очень быстро захватит мир.staticlab
27.05.2019 17:50+1А вот насчёт 1000 строк — это зависит. К примеру, когда я просматриваю события системы мониторинга или журналы — то бывает и по 10 тыс. строк, это гораздо удобней чем листать по 100.
Я говорил про оптимизацию по вьюпорту, а не про пагинацию. Вы одновременно на странице 10 тыс. строк видеть не можете, максимум, допустим, 100. Зачем загружать ими браузер? Намного лучше завести пул видимых строк и динамически их перерисовывать. Вы пытаетесь «оптимизировать» в одном месте, но при этом просаживаете производительность в другом.
Tangeman
27.05.2019 18:45Наличие кастомного viewport, как минимум, лишает возможности использовать поиск средствами браузера, так что с этой точки зрения от пагинации он не отличается.
В случае же если хочется их смотреть оффлайн — всё равно придётся вытащить всё сразу, все 10 тыс, так почему бы сразу их и не показать, благо скроллинг и поиск встроены и шустрее любой реализации на js?
Впрочем, речь не только о больших таблицах, посмотрите на любой гуглосайт (типа почты) — там при адекватном подходе количество кода (и маркапа) можно сократить минимум на порядок.
Да или просто первая страница яндекса — всё влезает на один экран, но там только одого js уже 500 кб (причём минимизированного) — вот о чём речь.
Простая, банальная страница после «фреймворкизации» превращается в монстра с кучей кода и горой модифицированных и распухших элементов, каждый из которых получает кучу классов и прочих атрибутов.
Может, вы привыкли смотреть страницы на чём-то с процом типа i8700 и 32GB памяти, но есть масса людей у которых всего 2G (из них половина сожрана системой, и ещё четверть всякой служебной фигнёй) и нечто вроде атома/арма — там всё это вызывает большую боль.
С этой точки зрения, Svelte выглядит более оптимальным вариантом, чем тот же react, но слегка жаль что не продолжает идею более глубокой оптимизации. Впрочем, возможно это и появится позже.
staticlab
27.05.2019 19:09+1В случае же если хочется их смотреть оффлайн — всё равно придётся вытащить всё сразу, все 10 тыс, так почему бы сразу их и не показать, благо скроллинг и поиск встроены и шустрее любой реализации на js?
Расскажите это пользователям Хрома здесь.
Впрочем, речь не только о больших таблицах, посмотрите на любой гуглосайт (типа почты) — там при адекватном подходе количество кода (и маркапа) можно сократить минимум на порядок.
Считайте, что гуглопочту такой сделали менеджеры. В Гугле сейчас выстроены совершенно неадекватные технологически, но эффективные с точки зрения бизнеса процессы. Тем не менее, гуглоофис — пример технологически сильных проектов, где для высокой производительности ребятам пришлось рендерить всё самостоятельно на канвасе.
Да или просто первая страница яндекса — всё влезает на один экран, но там только одого js уже 500 кб (причём минимизированного) — вот о чём речь.
Есть предположение, что это скрипты яндексовской рекламы и фреймворки тут ни при чём.
Простая, банальная страница после «фреймворкизации» превращается в монстра с кучей кода и горой модифицированных и распухших элементов, каждый из которых получает кучу классов и прочих атрибутов.
Гора элементов с кучей классов и атрибутов не имеет никакого отношения к фреймворкам.
Может, вы привыкли смотреть страницы на чём-то с процом типа i8700 и 32GB памяти, но есть масса людей у которых всего 2G (из них половина сожрана системой, и ещё четверть всякой служебной фигнёй) и нечто вроде атома/арма — там всё это вызывает большую боль.
Вы хотите обвинить меня в том, что я пишу неоптимизированные приложения?
С этой точки зрения, Svelte выглядит более оптимальным вариантом, чем тот же react, но слегка жаль что не продолжает идею более глубокой оптимизации. Впрочем, возможно это и появится позже.
Сам Реакт весит не так уж и много. Большую часть кода в сложном приложении займут шаблоны компонентов, бизнес-логика и библиотеки зависимостей. Так что есть предположение, что большое приложение на Свелте будет не сильно меньше реактового.
Tangeman
27.05.2019 19:34Есть предположение, что это скрипты яндексовской рекламы и фреймворки тут ни при чём.
Рекламные фреймворки там кушают не более трети, судя по всему. К тому же, я назвал размер с учётом адблока, явно грузится меньше чем могло бы.
Гора элементов с кучей классов и атрибутов не имеет никакого отношения к фреймворкам.
Я так думаю, это зависит от фреймворка. Некотоые из них явно сгенерированные, так что не вижу кто ещё мог это сделать.
Вы хотите обвинить меня в том, что я пишу неоптимизированные приложения?
Извините, ничего личного — речь шла об абстрактном «вы».
Большую часть кода в сложном приложении займут шаблоны компонентов, бизнес-логика и библиотеки зависимостей.
Какая бизнес-логика? Это UI, клиентская часть — там не должно быть бизнес-логики, разве что минимальная валидация. Задача браузера — это быстро и эффективно отображать UI, с учётом динамического изменения элементов — всё. Любой js который к нему отправляется должен решать только эту задачу, не более. Конечно, SPA/WebApp слегка другой вопрос, но фреймворки в основном используются не для SPA, и логика всё равно на стороне сервера.staticlab
27.05.2019 20:33+1Рекламные фреймворки там кушают не более трети, судя по всему. К тому же, я назвал размер с учётом адблока, явно грузится меньше чем могло бы.
Там сейчас все имена файлов обфусцированы, они не режутся адблоком.
Я так думаю, это зависит от фреймворка. Некотоые из них явно сгенерированные, так что не вижу кто ещё мог это сделать.
Если речь про сайт Яндекса, то там разве что БЭМифицированные классы, но это больше про вёрстку, чем про скрипты.
Какая бизнес-логика? Это UI, клиентская часть — там не должно быть бизнес-логики, разве что минимальная валидация. Задача браузера — это быстро и эффективно отображать UI, с учётом динамического изменения элементов — всё. Любой js который к нему отправляется должен решать только эту задачу, не более. Конечно, SPA/WebApp слегка другой вопрос, но фреймворки в основном используются не для SPA, и логика всё равно на стороне сервера.
Фреймворки как раз-таки используются в основном для SPA. И то, что там не должно быть никакой бизнес-логики — это вы видимо никогда не видели реальный фронтенд-проект. В части бизнес-логики это изменение состояния компонентов в соответствии с бизнес-процессом и корректная обработка ошибок как пользователя, так и сервера. Даже за простым интерфейсом может быть скрыта достаточно сложная логика поведения. Ещё не всегда можно что-либо рассчитывать на сервере просто потому, что будет заметная временная задержка на запрос по сети, и интерфейс будет визуально «подтормаживать».
Вдобавок, не всегда сервер отдаёт готовые к отображению данные и приходится их преобразовывать. Не поверите, но иногда бэкендерам это даже выгодно — не нужно париться по поводу агрегирующих сервисов, а клиентские устройства можно распределённо нагрузить маппингом данных. То есть они вот все такие крутые с микросервисами, очередями, строгой типизацией и прочей бигдатой, а потом эти данные сводит воедино «недоязычок» на мобильнике.
А ещё раньше у нас вот было форматирование телефонов на клиенте. Казалось бы, что тут такого, но библиотека для этого форматирования очень жирная, поскольку в ней собраны правила форматирования номеров для всего мира. Хорошо, хоть сейчас её перенесли на сервер. Так и живём :)
timdorohin
27.05.2019 02:34Скорость, размер…
А есть сравнение с Preact?
Он компактый и шустрый. Интересно, насколько тяжел Svelte в случае десятка компонент?
kidar2
27.05.2019 09:07Возможно пропустил, а как с отладкой обстоят дела? Или придётся отлаживать скомпилированный код?
AlexxNB Автор
27.05.2019 09:32Sourcemaps имеются — всё в порядке.
Скриншотstaticlab
27.05.2019 09:39Что-то не понял, как создавать байндинги на aria- и data-атрибуты. Кто подскажет?
AlexxNB Автор
27.05.2019 10:35Такого нет. Покажите, пожалуйста, кейс для примера. Может оно и не надо =)
staticlab
27.05.2019 12:56Вот упрощённый пример разметки select-компонента в React-стиле:
return ( <div data-test={`select_${selId}`}> <div tabIndex={disabled ? null : 0} role="button" aria-haspopup="listbox" aria-expanded={isOpen} aria-labelledby={labelId} aria-describedby={valueId} aria-disabled={disabled} > <div id={labelId}>{label}</div> <div id={valueId}>{selectedValue}</div> </div> <div data-test={`select_${selId}_listbox`} role="listbox" > {options.map((option, index) => ( <div data-test={`select_${selId}_option_${option.value}`} key={option.id} role="option" aria-selected={option.selected} aria-setsize={options.length} aria-posinset={index + 1} > {option.text} </div> ))} </div> </div> );
Через data-test мы размечаем страницу для e2e-тестов.
AlexxNB Автор
27.05.2019 14:08А, ну так в Svelte то же самое же. Любому атрибуту можно задавать javascript выражение. Просто это вроде не называется привязкой.
<div ... aria-selected={option.selected} ... </div>
staticlab
27.05.2019 14:22Да, прошу прощения, байндинг — это двустороння привязка. А односторонняя — это просто динамический атрибут.
kidar2
27.05.2019 11:48Такой вопрос, а почему бы не сделать расширение для TypeScript или его форк для решения ваших задач? Так бы вы сэкономили время
Ni55aN
27.05.2019 16:37Есть шанс, что появится альтернатива Ember-like условному рендерингу, циклам и прочему? Имхо подобные конструкции в виде директив выглядят более читаемыми, не создавая лишние вложенности.
AlexxNB Автор
27.05.2019 16:52На вкус и цвет все фломастеры разные. Всем сразу не угодишь.
Но, вероятно, при желании можно придумать препроцессор, для подобных хотелок.
alexesDev
Я о Svelte узнал от знакомого, посмотрел доку, интересно.
Но стиль ваших статей вызывает только негативные эмоции.
AlexxNB Автор
Благодарю за критику, замечу, что это всё же перевод. Вам не нравится сам стиль речи автора и моего перевода, или то, что опять часть статьи посвящена противопоставлению Svelte и React?
alexesDev
Думаю стиль автора, но раз вы перевели, то солидарны.
А сравнение выглядит как jQuery vs React, которое большей частью бессмысленно, разные вещи. Показывайте профит, если он есть, без
поливания грязьюупоминания "конкурентов" и все ок будет.AlexxNB Автор
К сожалению, все кто пытаются рассказывать про Svelte, в той или иной степени опираются на сравнение с традиционными фреймворками. Иначе просто не ответить на вопрос — "А зачем нам ещё один фреймворк?" То, что сравнивают в основном с React, лишь говорит о его популярности и известности. По-моему, заголовок статьи вполне хорошо раскрыт и без излишних оскорблений React в этой области.
bano-notit
Это заблуждение. Ответить на вопрос можно показав результаты бенчмарков или удобство конкретного подхода к задаче. Ну и конечно же рассказав про подводные камни. Без всего этого — это просто публицистика.
Вот пример статьи которая как раз хорошо рассказывает про технологию и рассказчик достаточно прошарен в ней, чтобы не брать конкретные примеры, а оперировать разными подходами и идеологиями из других языков не прибегая к самим яп: Знакомство с Python для камрадов, переросших «язык A vs. язык B» и другие предрассудки.
0xd34df00d
То, кстати, странная статья. По крайней мере, лично я из нее не понял, в чем профит перехода с моих привычных языков.
Busla
Т.е. вам нужны «бенчмарки» без сравнения с с традиционными <alt_tech_name>. Или в чём заблуждение?
bano-notit
Для меня есть 2 вида статей о технологии:
bano-notit
Если кратко, то вы не увидели союза "или" в моём высказывании.