[Перевод] Сравнение JS-фреймворков: React, Vue и Hyperapp
Автор материала, перевод которого мы сегодня публикуем, полагает, что Hyperapp — это заслуживающая внимания альтернатива таким веб-фреймворкам, как React или Vue. Он говорит, что причиной такого утверждения стало то, что он выяснил, что Hyperapp легче в освоении, чем эти два фреймворка. Его идея подверглась критике, так как кажется, что основана она исключительно на его мнении, а такой подход попросту не даёт другим фреймворкам возможности показать их сильные стороны. Эта статья направлена на объективный анализ Hyperapp, React и Vue, проведённый на основе простых примеров, демонстрирующих их возможности, и на основе результатов их испытаний.
Пример№1: приложение-счётчик
Реализация приложения-счётчика, вероятно, является одним из самых часто используемых примеров в реактивном программировании. Он предельно прост и понятен:
- Нам понадобится переменная
count
, которая будет хранить значение счётчика. - Нужны будут два метода, позволяющих инкрементировать и декрементировать переменную
count
. - Требуется механизм для вывода значения, хранящегося в
count
и представления его пользователю. - Нужны две кнопки, привязанные к соответствующим методам, позволяющие пользователю воздействовать на переменную
count
.
Вот реализация этого примера с использованием рассматриваемых фреймворков.
▍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 (
{this.state.count}
);
}
}
ReactDOM.render( , 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(
{this.count}
);
},
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) => (
{state.count}
);
app(state, actions, view, document.querySelector("#app"));
▍Анализ
Если вы не знакомы с этими фреймворками, или хотя бы с одним из них, то тут, вероятно, вам встретится кое-что непонятное. Поэтому давайте разберём этот код.
- При использовании всех трёх фреймворков в начале кода приложения имеются команды
import
. - В React используется объектно-ориентированная парадигма. Тут создаётся класс для компонента
Counter
. Vue идёт похожим путём. Тут создаётся новый экземпляр классаVue
, ему передаётся информация. И, наконец, в Hyperapp применяется функциональная парадигма, здесь используются самостоятельные сущностиview
,state
иactions
. - Если говорить о переменной
count
, то в React она инициализируется в конструкторе компонента, а в Vue и Hyperapp она представляет собой свойство, соответственно, объектовdata
иstate
. - Если продвинуться в исследовании этих приложений дальше, то можно заметить, что в React и Vue используются очень похожие методы для взаимодействия с переменной
count
. В React, для изменения состояния приложения, применяется методsetState
, унаследованный отReact.Component
. В Vue значениеthis.count
изменяется напрямую. Методы в Hyperapp написаны с использованием синтаксиса стрелочных функций ES6. Среди рассматриваемых фреймворков он единственный использует подобное, так как React и Vue вынуждены использовать внутри своих методов ключевое словоthis
. Методы Hyperapp, с другой стороны, требуют, чтобы им, в качестве аргумента, передавали бы объект с состоянием приложения. Это означает, что их, вероятнее всего, можно будет повторно использовать в различных контекстах. - Та часть приложения, которая отвечает за вывод данных на страницу, во всех трёх примерах выглядит практически одинаково. Особенность Vue заключается в том, что при использовании этого фреймворка в подсистему рендеринга надо передать функцию
h
. В Hyperapp, вместоonClick
, используетсяonclick
, а так же здесь обращение к переменнойcount
осуществляется не так как в React и Vue, что обусловлено особенностями того, как в каждом из фреймворков реализовано хранение состояния приложения. - И, наконец, все три фреймворка используют привязку к элементу
#app
. В каждом из них эта операция выполняется по-разному. Надо отметить, что в Vue эта операция выглядит самой простой и понятной и даёт разработчику более гибкую конструкцию, работая с селектором элемента, а не с самим элементом.
▍Выводы
Если напрямую сравнить код, решающий одну и ту же задачу, написанный с использованием всех трёх фреймворков, то окажется, что Hyperapp, для реализации приложения-счётчика, требуется меньше всего строк кода, и это — единственный фреймворк, использующий функциональный подход. Однако, объём кода, написанный c использованием Vue, если считать количество символов, получается немного меньше, да и использование в нём селектора элемента выглядит очень хорошо. Код React-приложения кажется самым длинным, но это не означает, что его понять намного сложнее, чем код, написанный для работы с другими анализируемыми фреймворками.
Пример№2: работа с асинхронным кодом
Вполне возможно, что вам, на практике, придётся иметь дело с асинхронным кодом. Одна из самых распространённых асинхронных операций представляет собой отправку запроса некоему API. Для целей этого примера применяется API JSONPlaceholder, содержащее условные данные и выдающее список публикаций. Вот что мы собираемся здесь сделать:
- Сохраним массив для размещения в нём публикаций (
posts
) в состоянии приложения. - Вызовем, воспользовавшись подходящим методом,
fetch()
, указав нужный нам URL, подождём поступления данных, распарсим полученный JSON-код, представляющий собой массив объектов, и, наконец, обновим переменнуюposts
, записав в неё полученные данные. - Выведем на страницу кнопку, которая вызывает метод, загружающий список публикаций.
- Выведем список публикаций из
posts
с использованием ключей.
Рассмотрим код, реализующий вышеописанную схему действий.
▍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 (
{this.state.posts.map(post => (
{post.title}
{post.body}
))}
);
}
}
ReactDOM.render( , 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 (
{this.posts.map(post => (
{post.title}
{post.body}
))}
);
},
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) => (
{state.posts.map(post => (
{post.title}
{post.body}
))}
);
app(state, actions, view, document.querySelector("#app"));
▍Анализ
Разберём этот код и сравним три исследуемые фреймворка.
- Так же, как и в предыдущем примере, хранение состояния приложения, вывод данных и подключение к элементу страницы, во всех трёх фреймворках весьма схожи. Тут наблюдаются те же различия, о которых мы уже говорили выше.
- Загрузка данных с помощью функции
fetch()
— операция довольно простая, она работает так, как ожидается, во всех фреймворках. Основное различие здесь, однако, заключается в том, что Hyperapp поддерживает выполнение асинхронных операций немного не так, как другие фреймворки. Вместо того чтобы модифицировать состояние напрямую, внутри асинхронного действия, это действие вызывает другое, синхронное действие, которое получает данные и преобразует их в подходящий формат. Это делает ядро приложения более функциональным и лучше подходящим для разбиения на небольшие части, которые, потенциально, подходят для повторного использования. Такой подход, кроме того, помогает избегать некоторых проблем, свойственных вложенным коллбэкам, которые могут возникнуть в ситуациях, подобных рассматриваемым. - Если говорить о размерах кода, то Hyperapp-приложению снова нужно меньше строк кода для достижения той же цели, но код на Vue выглядит более кратким, и, если посчитать количество символов кода, он короче других вариантов.
▍Выводы
Выполнение асинхронных операций оказалось одинаково простым во всех фреймворках. Hyperapp может склонить разработчика к написанию более функционального и модульного кода при работе с асинхронными действиями, но два других фреймворка тоже отлично справляются с поставленной перед ними задачей, и, в этом плане, дают разработчику возможность выбора.
Пример №3: компонент элемента списка для To-Do-приложения
Вероятно, To-Do-приложения — это самый знаменитый пример в области реактивного программирования. По всей видимости, нечто подобное реализовано с использованием почти каждого из существующих фреймворков. Тут мы не будем реализовывать всё приложение. Вместо этого остановимся на простом компоненте без состояния для того, чтобы изучить возможности исследуемых фреймворков по созданию небольших строительных блоков веб-приложений, подходящих для повторного использования.
Рассмотрим реализацию компонента с применением исследуемых фреймворков. В этом примере мы, однако, расширим рассматриваемые варианты кода за счёт рассмотрения React-компонента, написанного в функциональном стиле.
▍React (функциональный стиль)
function TodoItem(props) {
return (
props.toggle(props.id)}>
{props.value}
);
}
▍React
class TodoItem extends React.Component {
render () {
return (
this.props.toggle(this.props.id)}>
{this.props.value}
);
}
}
▍Vue
var TodoItem = Vue.component("todoitem", {
props: ["id", "value", "done", "toggle"],
template:
'{{value}} '
});
▍Hyperapp
Обратите внимание на то, что Hyperapp так же использует функциональный стиль.
const TodoItem = ({ id, value, done, toggle }) = (
toggle(id)}>
{value}
);
▍Анализ
- React, в том, что касается использования паттернов кодирования, является самым гибким фреймворком. Он поддерживает функциональные компоненты, а так же компоненты, оформленные в виде классов. Кроме того, React, в его стандартном виде, поддерживает и компоненты Hyperapp.
- Hyperapp тоже поддерживает функциональные React-компоненты. Это означает, что при работе с Hyperapp и React имеется большое пространство для экспериментов.
- Vue в этом испытании занимает последнее место. У него довольно странный синтаксис, который непросто сразу понять даже тем, кто знаком с React или Hyperapp.
- Если говорить о длине кода, то все примеры имеют очень похожие размеры. Единственное, что тут можно отметить — это то, что код на React, в одном из вариантов, получился немного объёмнее, чем в другом.
▍Выводы
Для того чтобы привыкнуть к Vue, нужно некоторое время, так как его шаблоны немного отличаются от шаблонов двух других фреймворков. React чрезвычайно гибок, он поддерживает различные подходы, применяемые при создании компонентов. В то же время, в Hyperapp всё устроено очень просто, и он, к тому же, совместим с React, что позволяет, при необходимости, на каком-то этапе проекта, сменить фреймворк.
Сравнение методов жизненного цикла компонентов
Ещё одно важное соображение, влияющее на выбор фреймворка, заключается поддерживаемых им событиях жизненного цикла компонентов, на которые можно подписываться и которые можно обрабатывать в соответствии с нуждами разработчика. Вот таблица, созданная на основе анализа API исследуемых систем.
Событие |
React |
Vue |
Hyperapp |
Инициализация |
constructor |
beforeCreate, created |
— |
Монтирование |
comoinentDidMount |
beforeMount, mounted |
oncreate |
Обновление |
componentDidUpdate |
beforeUpdate, updated |
onupdate |
Размонтирование |
componentWillUnmount |
— | onremove |
Уничтожение |
— | beforeDestroy, destroyed |
ondestroy |
▍Анализ
Вот что можно понять, проанализировав эту таблицу:
- Больше всего хуков жизненного цикла имеется в Vue. С их помощью у программиста есть возможность обработать всё, что происходит с компонентом, либо до вызова соответствующего события, либо после. Это может оказаться кстати для управления сложными компонентами.
- Хуки жизненного цикла React и Hyperapp очень похожи, хотя в React объединяет обработку событий, возникающих при размонтировании и уничтожении компонента, а Hyperapp так же поступает с событиями создания и монтирования компонента. И тот и другой дают разработчику достаточное количество возможностей по обработке событий жизненного цикла.
- Vue не обрабатывает событие размонтирования (насколько это можно понять, проанализировав API фреймворка), вместо этого полагаясь на хуки, связанные с уничтожением компонента. React не обрабатывает событие уничтожения компонента, позволяя обрабатывать лишь событие размонтирования компонента. Hyperapp не предлагает хуков для обработки события создания компонента, вместо этого полностью полагаясь на событие монтирования. В зависимости от ваших нужд и опыта эти различия стоит учитывать при проектировании приложения с учётом возможности обработки событий жизненного цикла компонентов.
▍Выводы
В целом можно отметить, что способы обработки событий, возникающих в ходе жизненного цикла компонентов, поддерживают все фреймворки. Эти способы позволят решать множество задач. Все три фреймворка предлагают хуки для всех видов событий, но между ними есть незначительные отличия, источником которых могут служить внутренние особенности фреймворков и различия в их реализации. Пожалуй, Vue в этой области находится на шаг впереди других систем, предлагая более детализированную систему обработки событий, позволяя обрабатывать события жизненного цикла либо до их возникновения, либо после.
Сравнение производительности фреймворков
Помимо удобства использования фреймворка и применяемых при работе с ним приёмов программирования, многих разработчиков серьёзно заботит и производительность фреймворков, особенно — для достаточно сложных приложений. Ценным источником сведений о производительности различных фреймворков служит проект js-framework-benchmark.
Поэтому взглянем на результаты тестирования React, Vue и Hyperapp.
▍Работа с таблицами
Вот результаты испытаний фреймворков на предмет работы с таблицами. Показатель в ячейках таблицы соответствует длительности выполнения операции ± среднеквадратическое отклонение. В скобках приведён результат деления полученного показателя на самый лучший показатель.
Анализ
- Операции, в которых для вывода данных не используются ключи (non-keyed) оказываю гораздо быстрее операций, в которых ключи применяются (keyed).
- Самым быстрым из всех шести рассмотренных вариантов оказался вариант, в котором используется React без применения ключей, показывающий впечатляющую производительность во всех тестах.
- Если сравнить Vue и React при работе с использованием ключей, то у Vue тут имеется небольшое преимущество. В то же время, если сопоставить React и Vue в вариантах, где ключи не используются, Vue показывает значительно меньшую производительность, чем React.
- У Vue и Hyperapp, как можно видеть из результатов, имеются какие-то сложности с тестом, в котором производится частичное обновление таблицы (partial update), а React показывает себя на нём хорошо, вероятно, из-за некоей оптимизации, направленной на ускорение подобных операций.
▍Загрузка, запуск, размеры кода
Вот таблица с результатами исследования показателей, имеющих отношение к скорости запуска фреймворка, к его размерам, к использованию им главного потока.
Анализ
- Код Hyperapp оказался самым маленьким среди исследуемых фреймворков. Код React и Vue имеет примерно одинаковые размеры.
- Hyperapp нужно меньше всего времени на запуск. Причиной этого, определённо, является маленький размер кода фреймворка и минималистичный подход в проектировании его API.
- Vue, если говорить о времени, необходимом на запуск фреймворка, оказывается немного быстрее, чем React.
▍Работа с памятью
Теперь рассмотрим результаты тестирования выделения памяти.
Анализ
- Hyperapp оказывается самым нетребовательным фреймворком в плане потребления памяти.
- В целом можно отметить, что все фреймворки потребляют не особенно много памяти. Это говорит о том, что они, на современных компьютерах, будут работать примерно одинаково.
▍Выводы
Если, при разработке некоего проекта, нужно добиться максимальной производительности, вам стоит разобраться, во-первых, с тем, что за приложение вы разрабатываете, а во-вторых — чётко выяснить нужды этого приложения. Если свести воедино анализ производительности всех трёх фреймворков, то возникает такое ощущение, что Vue и React лучше подходят для более сложных приложений, а Hyperapp лучше показывает себя на приложениях меньшего масштаба, в которых нужно обрабатывать меньше данных, которым требуется как можно более быстрый запуск, и которым может понадобиться работать на не самых быстрых компьютерах.
Однако тут стоит помнить, что использованные здесь тесты производительности далеки от реальной жизни, от некоего усреднённого сценария. Поэтому, испытав и сравнив их на реальном проекте, вы можете увидеть другие результаты.
Дополнительные замечания
Надо отметить, что сравнение веб-фреймворков может напоминать нечто вроде сравнения яблок с апельсинами. Вот ещё некоторые соображения, касающиеся React, Vue и Hyperapp, которые могут оказаться полезными при выборе конкретного фреймворка для некоего проекта:
- React обходит проблему, связанную с тем, что соседние JSX-элементы должны быть обёрнуты в родительский элемент, вводя концепцию фрагментов — элементов, которые позволяют группировать набор элементов-потомков без добавления в DOM дополнительных узлов.
- React даёт в распоряжение разработчика компоненты высшего порядка, в то время как в Vue, для целей повторного использования функционала компонентов, применяются миксины.
- Vue более полно задействует концепцию разделения ответственности, разделяя структуру и функционал приложения с использованием шаблонов.
- Hyperapp, если сравнивать его с React и Vue, выглядит как система, предоставляющая API более низкого уровня. Код Hyperapp-приложений оказывается короче, он даёт большую гибкость, что может быть полезно в тех случаях, когда разработчику может захотеться заняться его тонкой настройкой и исследованием его внутренних механизмов.
Итоги
Автор этого материала полагает, что, если вы дочитали до этого места, то у вас уже сформировалось понимание того, какой из исследованных здесь фреймворков лучше всего соответствует вашим нуждам. В конце концов, мы говорили не о том, какой из фреймворков является самым лучшим, а скорее о том, какой из них способен лучше всего показать себя в различных ситуациях. В итоге же можно сделать следующие общие выводы:
- React — это очень мощный инструмент, вокруг него сложилось большое сообщество разработчиков, которое, если у вас возникнут какие-то проблемы, вполне может помочь вам в их решении. Этот фреймворк не так уж и сложно освоить, но, для того, чтобы овладеть им в совершенстве, понадобится немало времени. Однако, React — замечательный фреймворк и он стоит усилий, потраченных на его изучение.
- Vue может выглядеть немного странным, если раньше вы использовали ещё какой-нибудь JavaScript-фреймворк, но это — весьма интересный инструмент. Он является достойной альтернативой React, и его, вполне возможно, стоит изучить, если React вам по каким-то причинам не вполне подходит. Vue имеет некоторые очень хорошие встроенные возможности, а его сообщество растёт, вероятно, даже быстрее, чем сообщество React.
- И, наконец, Hyperapp — это маленький приятный фреймворк, подходящий для небольших проектов, а так же — для начинающих разработчиков. Набор предоставляемых им инструментов меньше, чем у React или Vue, но его возможностей достаточно для быстрой разработки приложений или их прототипов. В ходе работы с ним можно разобраться с основами, на которых базируются другие фреймворки. Немалые объёмы кода, написанного для Hyperapp, совместимы с другими двумя фреймворками, причём, они могут использоваться там либо в том же виде, в котором присутствуют в Hyperapp-приложениях, либо с незначительными изменениями. В результате, если, начав проект на Hyperapp, вы поймёте, что вам нужно нечто большее, то, параллельно осваивая React или Vue, вы сможете без особых проблем перейти на другой фреймворк.
Уважаемые читатели! Как вы подбираете фреймворки для веб-проектов?
Всё верно, это промо-код для скидки в 10% на наши виртуальные сервера :)