Это небольшая ответная статья на публикацию «Сравнение JS-фреймворков: React, Vue и Hyperapp». Вообще я не большой фанат подобных сравнений. Однако раз уж речь зашла о таком маргинальном фреймворке, как Hyperapp, в сравнении с мастодонтами, типа React и Vue, я подумал, почему бы не рассмотреть все те же примеры на Svelte. Так сказать, для полноты картины. Тем более, это займет буквально 5 минут. Поехали!

Если вдруг вы не знакомы со Svelte и концепцией исчезающих фреймворков, можете ознакомиться со статьями «Магически исчезающий JS фреймворк» и «Исчезающие фреймворки».
Для удобства читателей, я скопировал примеры из оригинальной статьи под спойлеры, чтобы было удобнее сравнивать. Что ж приступим.
→ Рабочий пример.
Компонент Svelte — это html-файл, который имеет небезызвестный формат Single File Component (SFC), в том или ином виде, уже применяющийся в Vue, Ractive, Riot и некоторых других фреймворках. Кроме самого html-шаблона, компонент может иметь поведение и логику, описанную на javascript, а также scoped-стили компонента.
Ни одна часть компонента не является обязательной, поэтому компонент счетчика может состоять лишь из html-шаблона самого счетчика. Для изменения значения переменной count, обработчик кликов использует встроенный метод компонента set(), описанный в документации.
→ Рабочий пример.
В отличие от JSX, который, как под копирку, применяется во всех 3-х фреймворках из оригинального сравнения, и по сути расширяет javascript код html-подобным синтаксисом, Svelte использует более привычные возможности — внедрение js и css кода в html с помощью тегов <script> и <style>.
Скрипт компонента экспортирует простой JS объект, разделенный на секции. В секции methods описываются методы компонента, которые могут быть использованы через инстанс компонента и в обработчиках событий. Так, при нажатии на кнопку, вызывается метод getData(), внутри которого запрашиваются данные и по завершению операции, данные просто устанавливаются в стейт компонента и сразу же отрисовываются в шаблоне.
Обратите внимание, на использование деструктуризации объекта публикации (post) на каждом шаге итерации списка публикаций:
Этот трюк позволяет избежать избыточности в шаблонах типа {post.title} и визуально упростить шаблоны, используя более короткую запись {title}.
→ Рабочий пример.
Тут все довольно банально. Выставляем css-класс в зависимости от значения done и меняем это значение на противоположное при клике на элемент списка.
Disclaimer: С этого момента я решил опустить сравнение с Hyperapp, потому что иначе таблицы будут просто не читаемые.
Svelte крайне минималистичен, в том числе в плане хуков жизненного цикла. Существует всего 4 хука:
Честно говоря, не знаю что тут комментировать. Сами по себе бенчмарки и способ их исполнения всегда вызывает много споров, поэтому не думаю что имеет смысл излишне заострять внимание.
Однако других данных у нас все равно нет.



Cудя по цифрам, Svelte довольно быстрый, «жрет» мало памяти (в том числе потому что не использует VirtualDOM), быстро запускается и имеет небольшие размеры.
Вообще, сказать что результаты бенчмарков этих 3-х фреймворков отличались кардинально, я не могу. Однако, только Svelte имеет исключительно «зеленый» столбик, т.е. он достаточно хорош сразу по всем показателям, а значит отлично сбалансирован и не имеет явных перекосов ни в скорости, ни в потреблении памяти и иных метриках. В общем, с ним смело можно начинать любой проект, от обычного веба, до мобильных устройств, Смарт ТВ и более экзотичных систем.

Svelte — отличный инструмент для создания компонентов практически любых веб-приложений. Он такой же мощный как React и хотя пока имеет значительно меньшее сообщество, при этом требует меньше усилий для освоения. Он гибок, как Vue, при этом значительно более минималистичен и выверен, так как не пытается ухватиться сразу за все идеи современной веб-разработки.
Кроме того, он единственный использует концепцию исчезающих фреймворков. Иными словами он не имеет никакого специфического рантайма, кроме самого браузера.
Приложения на нем получаются быстрыми, не требовательными к ресурсам и маленькими в размере. Кроме того, Svelte может «бесшовно» использоваться в приложениях, написанных на других фреймворках, о чем я планирую написать в следующий раз.
Если вы впервые познакомились с этим замечательным фреймворком, возможно вам будет интересно прочитать и другие статьи о нем:
→ Магически исчезающий JS фреймворк
→ 1Kb autocomplete
→ SvelteJS: Релиз второй версии
Если у вас остались вопросы по Svelte, вступайте в русскоязычный телеграм канал SvelteJS. Там вы всегда сможете задать вопрос и получить совет, узнать последние новости, а также просто приятно пообщаться. Будем вам рады!

Если вдруг вы не знакомы со Svelte и концепцией исчезающих фреймворков, можете ознакомиться со статьями «Магически исчезающий JS фреймворк» и «Исчезающие фреймворки».
Для удобства читателей, я скопировал примеры из оригинальной статьи под спойлеры, чтобы было удобнее сравнивать. Что ж приступим.
Пример №1: приложение-счётчик
React
import React from "react"; import ReactDOM from "react-dom"; class Counter extends React.Component { constructor(props) { super(props); this.state = { count: 0}; } down(value) { this.setState(state => ({ count: state.count - value })); } up(value) { this.setState(state => ({ count: state.count + value })); } render() { return ( <div> <h1>{this.state.count}</h1> <button onClick = {() => this.down(1)}>-</button> <button onClick = {() => this.up(1)}>+</button> </div> ); } } ReactDOM.render(<Counter />, document.querySelector("#app"));
Vue
import Vue from "vue"; new Vue({ data: { count: 0 }, methods: { down: function(value) { this.count -= value; }, up: function(value) { this.count += value; } }, render: function(h) { return( <div> <h1>{this.count}</h1> <button onClick={() => this.down(1)}>-</button> <button onClick={() => this.up(1)}>+</button> </div> ); }, el: "#app" });
Hyperapp
import { h, app } from "hyperapp"; const state = { count: 0 }; const actions = { down: value => state => ({ count: state.count - value}), up: value => state => ({ count: state.count + value}) }; const view = (state, actions) => ( <div> <h1>{state.count}</h1> <button onclick={() => actions.down(1)}>-</button> <button onclick={() => actions.up(1)}>+</button> </div> ); app(state, actions, view, document.querySelector("#app"));
▍Svelte
<div> <h1>{count}</h1> <button on:click="set({count: count - 1})">-</button> <button on:click="set({count: count + 1})">+</button> </div>
→ Рабочий пример.
▍Анализ
Компонент Svelte — это html-файл, который имеет небезызвестный формат Single File Component (SFC), в том или ином виде, уже применяющийся в Vue, Ractive, Riot и некоторых других фреймворках. Кроме самого html-шаблона, компонент может иметь поведение и логику, описанную на javascript, а также scoped-стили компонента.
Ни одна часть компонента не является обязательной, поэтому компонент счетчика может состоять лишь из html-шаблона самого счетчика. Для изменения значения переменной count, обработчик кликов использует встроенный метод компонента set(), описанный в документации.
Пример №2: работа с асинхронным кодом
React
import React from "react"; import ReactDOM from "react-dom"; class PostViewer extends React.Component { constructor(props) { super(props); this.state = { posts: [] }; } getData() { fetch(`https://jsonplaceholder.typicode.com/posts`) .then(response => response.json()) .then(json => { this.setState(state => ({ posts: json})); }); } render() { return ( <div> <button onClick={() => this.getData()}>Get posts</button> {this.state.posts.map(post => ( <div key={post.id}> <h2><font color="#3AC1EF">{post.title}</font></h2> <p>{post.body}</p> </div> ))} </div> ); } } ReactDOM.render(<PostViewer />, document.querySelector("#app"));
Vue
import Vue from "vue"; new Vue({ data: { posts: [] }, methods: { getData: function(value) { fetch(`https://jsonplaceholder.typicode.com/posts`) .then(response => response.json()) .then(json => { this.posts = json; }); } }, render: function(h) { return ( <div> <button onClick={() => this.getData()}>Get posts</button> {this.posts.map(post => ( <div key={post.id}> <h2><font color="#3AC1EF">{post.title}</font></h2> <p>{post.body}</p> </div> ))} </div> ); }, el: "#app" });
Hyperapp
import { h, app } from "hyperapp"; const state = { posts: [] }; const actions = { getData: () => (state, actions) => { fetch(`https://jsonplaceholder.typicode.com/posts`) .then(response => response.json()) .then(json => { actions.getDataComplete(json); }); }, getDataComplete: data => state => ({ posts: data }) }; const view = (state, actions) => ( <div> <button onclick={() => actions.getData()}>Get posts</button> {state.posts.map(post => ( <div key={post.id}> <h2><font color="#3AC1EF">{post.title}</font></h2> <p>{post.body}</p> </div> ))} </div> ); app(state, actions, view, document.querySelector("#app"));
▍Svelte
<div> <button on:click="getData()">Get posts</button> {#each posts as {title, body}} <div> <h2><font color="#3AC1EF">{title}</font></h2> <p>{body}</p> </div> {/each} </div> <script> export default { methods: { getData() { fetch('https://jsonplaceholder.typicode.com/posts') .then(res => res.json()) .then(posts => this.set({ posts })); } } }; </script>
→ Рабочий пример.
▍Анализ
В отличие от JSX, который, как под копирку, применяется во всех 3-х фреймворках из оригинального сравнения, и по сути расширяет javascript код html-подобным синтаксисом, Svelte использует более привычные возможности — внедрение js и css кода в html с помощью тегов <script> и <style>.
Скрипт компонента экспортирует простой JS объект, разделенный на секции. В секции methods описываются методы компонента, которые могут быть использованы через инстанс компонента и в обработчиках событий. Так, при нажатии на кнопку, вызывается метод getData(), внутри которого запрашиваются данные и по завершению операции, данные просто устанавливаются в стейт компонента и сразу же отрисовываются в шаблоне.
Обратите внимание, на использование деструктуризации объекта публикации (post) на каждом шаге итерации списка публикаций:
{#each posts as {title, body}}
Этот трюк позволяет избежать избыточности в шаблонах типа {post.title} и визуально упростить шаблоны, используя более короткую запись {title}.
Пример №3: компонент элемента списка для To-Do-приложения
React (функциональный стиль)
function TodoItem(props) { return ( <li class={props.done ? "done" : ""} onclick={() => props.toggle(props.id)}> {props.value} </li> ); }
React
class TodoItem extends React.Component { render () { return ( <li class={this.props.done ? "done" : ""} onclick={() => this.props.toggle(this.props.id)}> {this.props.value} </li> ); } }
Vue
var TodoItem = Vue.component("todoitem", { props: ["id", "value", "done", "toggle"], template: '<li v-bind:class="{done : done}" v-on:click="toggle(id)">{{value}}</li>' });
Hyperapp
const TodoItem = ({ id, value, done, toggle }) = ( <li class={done ? "done" : ""} onclick={() => toggle(id)}> {value} </li> );
▍Svelte
<li class="{done ? 'done' : ''}" on:click="set({done: !done})">{value}</li>
→ Рабочий пример.
▍Анализ
Тут все довольно банально. Выставляем css-класс в зависимости от значения done и меняем это значение на противоположное при клике на элемент списка.
Сравнение методов жизненного цикла компонентов
Disclaimer: С этого момента я решил опустить сравнение с Hyperapp, потому что иначе таблицы будут просто не читаемые.
| Событие |
React |
Vue |
Svelte |
| Инициализация |
constructor |
beforeCreate, created |
onstate |
| Монтирование |
componentDidMount |
beforeMount, mounted |
oncreate, onupdate |
| Обновление |
componentDidUpdate |
beforeUpdate, updated |
onstate, onupdate |
| Размонтирование |
componentWillUnmount |
— | ondestroy |
| Уничтожение |
— | beforeDestroy, destroyed |
— |
▍Анализ
Svelte крайне минималистичен, в том числе в плане хуков жизненного цикла. Существует всего 4 хука:
- onstate — вызывается до создания компонента и каждое изменение стейта до обновления DOM.
- oncreate — вызывается момент создания компонента.
- onupdate — вызывается сразу после монтирования в DOM и каждое изменение стейта после обновления DOM.
- ondestroy — вызывается при уничтожении компонента и удаления из DOM.
Сравнение производительности фреймворков
Честно говоря, не знаю что тут комментировать. Сами по себе бенчмарки и способ их исполнения всегда вызывает много споров, поэтому не думаю что имеет смысл излишне заострять внимание.
Однако других данных у нас все равно нет.
▍Работа с таблицами

▍Загрузка, запуск, размеры кода

▍Работа с памятью

▍Анализ
Cудя по цифрам, Svelte довольно быстрый, «жрет» мало памяти (в том числе потому что не использует VirtualDOM), быстро запускается и имеет небольшие размеры.
Вообще, сказать что результаты бенчмарков этих 3-х фреймворков отличались кардинально, я не могу. Однако, только Svelte имеет исключительно «зеленый» столбик, т.е. он достаточно хорош сразу по всем показателям, а значит отлично сбалансирован и не имеет явных перекосов ни в скорости, ни в потреблении памяти и иных метриках. В общем, с ним смело можно начинать любой проект, от обычного веба, до мобильных устройств, Смарт ТВ и более экзотичных систем.

Итоги
Svelte — отличный инструмент для создания компонентов практически любых веб-приложений. Он такой же мощный как React и хотя пока имеет значительно меньшее сообщество, при этом требует меньше усилий для освоения. Он гибок, как Vue, при этом значительно более минималистичен и выверен, так как не пытается ухватиться сразу за все идеи современной веб-разработки.
Кроме того, он единственный использует концепцию исчезающих фреймворков. Иными словами он не имеет никакого специфического рантайма, кроме самого браузера.
Приложения на нем получаются быстрыми, не требовательными к ресурсам и маленькими в размере. Кроме того, Svelte может «бесшовно» использоваться в приложениях, написанных на других фреймворках, о чем я планирую написать в следующий раз.
Если вы впервые познакомились с этим замечательным фреймворком, возможно вам будет интересно прочитать и другие статьи о нем:
→ Магически исчезающий JS фреймворк
→ 1Kb autocomplete
→ SvelteJS: Релиз второй версии
Если у вас остались вопросы по Svelte, вступайте в русскоязычный телеграм канал SvelteJS. Там вы всегда сможете задать вопрос и получить совет, узнать последние новости, а также просто приятно пообщаться. Будем вам рады!
