Как стать автором
Обновить

Re: «Сравнение JS-фреймворков: React, Vue и Hyperapp»

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

image

Если вдруг вы не знакомы со 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 имеет исключительно «зеленый» столбик, т.е. он достаточно хорош сразу по всем показателям, а значит отлично сбалансирован и не имеет явных перекосов ни в скорости, ни в потреблении памяти и иных метриках. В общем, с ним смело можно начинать любой проект, от обычного веба, до мобильных устройств, Смарт ТВ и более экзотичных систем.

image

Итоги


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

Кроме того, он единственный использует концепцию исчезающих фреймворков. Иными словами он не имеет никакого специфического рантайма, кроме самого браузера.

Приложения на нем получаются быстрыми, не требовательными к ресурсам и маленькими в размере. Кроме того, Svelte может «бесшовно» использоваться в приложениях, написанных на других фреймворках, о чем я планирую написать в следующий раз.

Если вы впервые познакомились с этим замечательным фреймворком, возможно вам будет интересно прочитать и другие статьи о нем:

Магически исчезающий JS фреймворк
1Kb autocomplete
SvelteJS: Релиз второй версии

Если у вас остались вопросы по Svelte, вступайте в русскоязычный телеграм канал SvelteJS. Там вы всегда сможете задать вопрос и получить совет, узнать последние новости, а также просто приятно пообщаться. Будем вам рады!
Теги:
Хабы:
+21
Комментарии 85
Комментарии Комментарии 85

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн