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

в 23:46, , рубрики: Hyperapp, javascript, React, ReactJS, svelte, vue, vuejs, Разработка веб-сайтов

Это небольшая ответная статья на публикацию «Сравнение 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="#000">{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="#000">{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="#000">{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="#000">{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.

Сравнение производительности фреймворков

Честно говоря, не знаю что тут комментировать. Сами по себе бенчмарки и способ их исполнения всегда вызывает много споров, поэтому не думаю что имеет смысл излишне заострять внимание.
Однако других данных у нас все равно нет.

▍Работа с таблицами

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

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

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

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

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

▍Анализ

Cудя по цифрам, Svelte довольно быстрый, «жрет» мало памяти (в том числе потому что не использует VirtualDOM), быстро запускается и имеет небольшие размеры.

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

image

Итоги

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

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

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

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

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

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

Автор: PaulMaly

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js