Хабрахабр

[Перевод] Сравнение React и Vue на практическом примере

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

Для него поиск различий между фреймворками заключался не в выяснении того, поддерживают ли они виртуальную объектную модель документа, или того, как именно они рендерят страницы. Он почитал документацию, посмотрел несколько учебных видео, и, хотя всё это показалось ему весьма полезным, ему хотелось по-настоящему понять, в чём заключается разница между React и Vue. Он надеялся найти статью, которая посвящена раскрытию именно таких различий, прочтя которую тот, кто знал раньше лишь Vue или React (или совершенно новый в веб-разработке человек), мог бы лучше понять различия между этими фреймворками. Ему хотелось, чтобы кто-нибудь объяснил бы ему особенности кода, показал бы, что в нём происходит.

Этот факт привёл его к пониманию того, что ему самому надо взять и такую статью написать, попутно разобравшись в сходствах и отличиях React и Vue. Однако такой статьи ему найти не удалось. Собственно говоря, перед вами описание его эксперимента по сравнению этих двух фреймворков.

Общие положения

Vue или React?

Оба приложения разработаны с использованием стандартных CLI (create-react-app для React и vue-cli для Vue). Для проведения эксперимента я решил создать пару довольно стандартных To-Do-приложений, которые позволяют пользователю добавлять элементы в список дел и удалять их из него. CLI — это, если кто не знает, сокращение, которое расшифровывается как Command Line Interface, то есть — интерфейс командной строки.

Теперь предлагаю взглянуть на внешний вид приложений, о которых идёт здесь речь.

Приложения, созданные средствами Vue и React

Вот репозитории с кодом этих приложений: Vue ToDo, React ToDo.

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

Структура проектов, использующих Vue и React

Единственное серьёзное различие заключается в том, что у React-приложения имеется три CSS-файла, в то время как у Vue-приложения их нет совсем. Как можно заметить, структура этих двух проектов практически идентична. Причина подобного заключается в том, что, при использовании create-react-app, компоненты React оснащаются сопутствующими им CSS-файлами, а CLI Vue использует другой подход, когда стили объявляются внутри конкретного файла компонента.

На самом деле, тут всё сводится к личным предпочтениям того, кто создаёт веб-проект. В итоге и тот и другой подход позволяют достичь одной и той же цели, при этом, при желании, ничто не мешает организовать стили иначе, что в Vue, что в React. Сейчас мы просто будем следовать стандартным подходам к работе с CSS, заложенным в CLI рассматриваемых фреймворков. Например, тема структурирования CSS постоянно обсуждается в сообществах разработчиков.

Однако прежде чем мы пойдём дальше, давайте взглянем на то, как выглядит типичный компонент Vue и React.

Вот код компонента Vue (в нашем проекте он находится в файле ToDoItem.vue).

<template> <div class="ToDoItem"> <p class="ToDoItem-Text">}</p> <div class="ToDoItem-Delete" @click="deleteItem(todo)">- </div> </div>
</template> <script> export default { name: "to-do-item", props: ['todo'], methods: { deleteItem(todo) { this.$emit('delete', todo) } } }
</script> <style> .ToDoItem { display: flex; justify-content: center; align-items: center; } .ToDoItem-Text { width: 90%; background-color: white; border: 1px solid lightgrey; box-shadow: 1px 1px 1px lightgrey; padding: 12px; margin-right: 10px; } .ToDoItem-Delete { width: 20px; padding: 5px; height: 20px; cursor: pointer; background: #ff7373; border-radius: 10px; box-shadow: 1px 1px 1px #c70202; color: white; font-size: 18px; margin-right: 5px; } .ToDoItem-Delete:hover { box-shadow: none; margin-top: 1px; margin-left: 1px; } </style>

Вот код React-компонента (файл ToDoItem.js).

import React, {Component} from 'react';
import './ToDoItem.css'; class ToDoItem extends Component { render() { return ( <div className="ToDoItem"> <p className="ToDoItem-Text">{this.props.item}</p> <div className="ToDoItem-Delete" onClick={this.props.deleteItem}>-</div> </div> ); }
} export default ToDoItem;

Теперь пришло время погрузиться в детали.

Как выполняется изменение данных?

Изменение данных ещё называют «мутацией данных». Речь идёт об изменениях, вносимых в данные, которые хранит наше приложение. Так, если нам нужно изменить имя некоего человека с «Джон» на «Марк», то речь идёт о «мутации данных». Именно в подходе к изменению данных и находится ключевое различие между React и Vue. А именно, Vue создаёт объект data, в котором находятся данные, и содержимое которого можно свободно менять. React же создаёт объект state, в котором хранится состояние приложения, и при работе с которым для изменения данных требуются некоторые дополнительные усилия. Однако в React всё устроено именно так не без причины, ниже мы об этом поговорим, а для начала рассмотрим вышеупомянутые объекты.

Вот как выглядит объект data, который используется в Vue.

data() { return { list: [ { todo: 'clean the house' }, { todo: 'buy milk' } ], } },

Вот как выглядит объект state, применяемый в React:

constructor(props) { super(props); this.state = { list: [ { 'todo': 'clean the house' }, { 'todo': 'buy milk' } ], }; };

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

Тут я присвоил свойству name моё собственное имя. Предположим, у нас имеется элемент данных вроде name: ‘Sunil’.

А вот как их поменять: this.name = ‘John’. В Vue обратиться к этим данным можно с помощью конструкции this.name. Не знаю точно, как бы я себя чувствовал, если бы моё имя и правда изменилось, но в Vue это работает именно так.

А вот поменять их, написав нечто вроде this.state.name = ‘John’, нельзя, так как в React действуют ограничения, предотвращающие подобные изменения данных. В React же обратиться к тем же данным можно с помощью конструкции this.state.name. Поэтому в React приходится пользоваться чем-то наподобие this.setState({name: ‘John’}).

В React приходится писать больше кода, но в Vue существует что-то вроде особой версии функции setState, которая вызывается при, как кажется, простом изменении данных. Результатом подобной операции является то же самое, что получается после выполнения более простой операции в Vue. Поэтому, если подвести итоги, React требует использования команды setState с передачей ей описания данных, которые надо изменить, а Vue работает, исходя из предположения, что разработчик хотел бы воспользоваться чем-то подобным, меняя данные внутри объекта data.

Ответы на эти вопросы можно узнать у Реванта Кумара: «Это так потому что React стремится повторно выполнить, при изменении состояния, определённые хуки жизненного цикла, такие, как componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate. Теперь зададимся вопросами о том, почему в React всё устроено именно так, и зачем вообще нужна функция setState. Если бы вы меняли состояние напрямую, React пришлось бы выполнять гораздо больше работы для отслеживания изменений, для определения того, какие хуки жизненного цикла надо запускать, и так далее. Он узнаёт о том, что состояние изменилось, когда вы вызываете функцию setState. В результате React, для того, чтобы облегчить себе жизнь, использует setState».

Теперь, когда с изменениями данных мы разобрались, поговорим о том, как, в обеих версиях нашего приложения, добавлять новые элементы в список дел.

Добавление новых элементов в список дел

▍React

Вот как это делается в React.

createNewToDoItem = () => { this.setState( ({ list, todo }) => ({ list: [ ...list, { todo } ], todo: '' }) );
};

Здесь у поля служащего для ввода данных (input), имеется атрибут value. Этот атрибут обновляется автоматически благодаря использованию пары взаимосвязанных функций, которые формируют то, что называется двусторонней привязкой данных (если вы о таком раньше не слышали — подождите немного, мы поговорим об этом в разделе, посвящённом добавлению элементов в Vue-приложении). Эту разновидность двусторонней связи мы создаём благодаря наличию дополнительного прослушивателя событий onChange, прикреплённому к полю input. Взглянем на код этого поля для того, чтобы вам было понятнее то, что здесь происходит.

<input type="text" value={this.state.todo} onChange={this.handleInput}/>

Функция handleInput вызывается при изменении значения поля input. Это приводит к обновлению элемента todo, который находится внутри объекта state, путём установки его в то значение, которое имеется в поле input. Вот как выглядит функция handleInput.

handleInput = e => { this.setState({ todo: e.target.value });
};

Теперь, когда пользователь нажимает на странице приложения кнопку + для добавления в список новой записи, функция createNewToDoItem вызывает метод this.setState и передаёт ему функцию. Эта функция принимает два параметра. Первый — это весь массив list из объекта state, а второй — это элемент todo, обновляемый функцией handleInput. Затем функция возвращает новый объект, который содержит прежний массив list, и добавляет новый элемент todo в конец этого массива. Работа со списком организована с использованием оператора spread (если вы с ним раньше не встречались — знайте, что это одна из новых возможностей ES6, и поищите подробности о нём).

И наконец, в todo записывается пустая строка, что автоматически обновляет значение value в поле input.

▍Vue

Для добавления нового элемента в список дел в Vue используется следующая конструкция.

createNewToDoItem() { this.list.push( { 'todo': this.todo } ); this.todo = '';
}

В Vue у поля ввода имеется директива v-model. Она позволяет организовать двустороннюю привязку данных. Взглянем на код этого поля и поговорим о том, что тут происходит.

<input type="text" v-model="todo"/>

Директива v-model привязывает поле к ключу, который имеется в объекте данных, который называется toDoItem. Когда страница загружается, в toDoItem записана пустая строка, выглядит это как todo: ‘’.

В любом случае, если вернуться к примеру с пустой строкой, текст, который мы введём в поле, попадёт, за счёт привязки данных, в свойство todo. Если тут уже имеются какие-то данные, нечто вроде todo: ‘add some text here’, то в поле ввода попадёт такой же текст, то есть — ‘add some text here’. Это и есть двусторонняя привязка данных, то есть, ввод новых данных в поле приводит к записи этих данных в объект data, а обновление данных в объекте приводит к появлению этих данных в поле.

Как можно видеть, мы помещаем содержимое todo в массив list, а затем записываем в todo пустую строку. Теперь вспомним функцию createNewToDoItem(), о которой мы говорили выше.

Удаление элементов из списка

▍React

В React эта операция выполняется так.

deleteItem = indexToDelete => { this.setState(({ list }) => ({ list: list.filter((toDo, index) => index !== indexToDelete) }));
};

В то время как функция deleteItem находится в файле ToDo.js, обратиться к ней без проблем можно и из ToDoItem.js, сначала передав эту функцию как свойство в <ToDoItem/>. Вот как это выглядит:

<ToDoItem deleteItem={this.deleteItem.bind(this, key)}/>

Тут мы сначала передаём функцию, что делает её доступной дочерним компонентам. Кроме того, мы осуществляем привязку this и передачу параметра key. Этот параметр используется функцией для того, чтобы отличить элемент ToDoItem, который нужно удалить, от других элементов. Затем, внутри компонента ToDoItem, мы делаем следующее.

<div className="ToDoItem-Delete" onClick={this.props.deleteItem}>-</div>

Всё, что нужно сделать для того, чтобы обратиться к функции, находящейся в родительском компоненте — это воспользоваться конструкцией this.props.deleteItem.

▍Vue

Удаление элемента списка в Vue выполняется так.

onDeleteItem(todo){ this.list = this.list.filter(item => item !== todo);
}

В Vue требуется несколько иной подход к удалению элементов, чем тот, которым мы пользовались в React. А именно, тут надо выполнить три действия.

Во-первых, вот что надо сделать в элементе, для которого нужно будет вызвать функцию его удаления.

<div class="ToDoItem-Delete" @click="deleteItem(todo)">-</div>

Затем нужно создать функцию emit в виде метода в дочернем компоненте (в данном случае — в ToDoItem.vue), которая выглядит следующим образом.

deleteItem(todo) { this.$emit('delete', todo)
}

Далее, вы можете заметить, что мы, добавляя ToDoItem.vue внутри ToDo.vue, обращаемся к функции.

<ToDoItem v-for="todo in list" :todo="todo" @delete="onDeleteItem" // <-- this :) :key="todo.id" />

Это — то, что называется пользовательским прослушивателем событий. Он реагирует на вызовы emit со строкой delete. Если он фиксирует подобное событие, он вызывает функцию onDeleteItem. Она находится внутри ToDo.vue, а не в ToDoItem.vue. Эта функция, как уже сказано выше, просто фильтрует массив todo, находящийся в объекте data для того, чтобы удалить из него элемент, по которому щёлкнули.

Выглядеть это может так. Кроме того, стоит отметить, что в примере с использованием Vue можно было бы просто написать код, относящийся к функции emit, внутри прослушивателя @click.

<div class="ToDoItem-Delete" @click="$emit(‘delete’, todo)">-</div>

Это уменьшило бы число шагов, необходимых для удаления элемента списка, с трёх до двух. Как именно поступить — вопрос предпочтений разработчика.

В Vue же дочерние компоненты должны вызывать события с помощью функции emit, а эти события уже обрабатываются родительским компонентом. Если подвести краткие итоги этого раздела, то можно сказать, что в React доступ к функциям, описанным в родительских компонентах, организуется через this.props (учитывая то, что props передаётся дочерним компонентам, что является стандартным приёмом, который можно встретить буквально повсюду).

Работа с прослушивателями событий

▍React

В React прослушиватели событий для чего-то простого, вроде события щелчка, весьма просты. Вот пример создания обработчика события click для кнопки, создающей новый элемент списка дел.

<div className="ToDo-Add" onClick={this.createNewToDoItem}>+</div>

Тут всё устроено весьма просто, это очень похоже на обработку подобных событий с использованием чистого JavaScript.

Тут нужно, чтобы событие onKeyPress обрабатывалось бы следующим образом. Надо отметить, что здесь настройка прослушивателей событий нажатий на кнопки клавиатуры, например, на Enter, занимает немного больше времени, чем в Vue. Вот код поля ввода.

<input type="text" onKeyPress={this.handleKeyPress}/>

Функция handleKeyPress вызывает функцию createNewToDoItem, когда распознаёт нажатие на Enter. Выглядит это так.

handleKeyPress = (e) => {
if (e.key === ‘Enter’) {
this.createNewToDoItem();
}
};

▍Vue

Обработчики событий в Vue настраивать весьма просто. Тут достаточно использовать символ @, а затем указать тип прослушивателя, который мы хотим использовать.

Например, для добавления прослушивателя события щелчка можно использовать следующий код.

<div class="ToDo-Add" @click="createNewToDoItem()">+</div>

Обратите внимание на то, что @click — это сокращение от v-on:click. Прослушиватели событий Vue хороши тем, что ими можно весьма тонко управлять. Например, если присоединить к прослушивателю конструкцию .once, это приведёт к тому, что прослушиватель сработает лишь один раз.

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

<input type="text" v-on:keyup.enter="createNewToDoItem"/>

Передача данных дочерним компонентам

▍React

В React свойства передаются дочернему компоненту при его создании. Например, так:

<ToDoItem key={key} item={todo} />

Здесь можно видеть два свойства, переданных компоненту ToDoItem. С этого момента к ним можно обращаться в дочернем компоненте через this.props.

Например, для того, чтобы обратиться к свойству item.todo, достаточно использовать конструкцию this.props.item.

▍Vue

В Vue свойства дочерним компонентам также передаются при их создании.

<ToDoItem v-for="todo in list" :todo="todo" :key="todo.id" @delete="onDeleteItem" />

После этого они передаются в массив props дочернего компонента, например, с помощью конструкции props: [ ‘todo’ ]. Обращаться к этим свойствам в дочерних компонентах можно по имени, в нашем случае это имя ‘todo’.

Передача данных родительскому компоненту

▍React

В React сначала дочернему компоненту передаётся функция, как свойство, там, где вызывается дочерний компонент. Затем выполняется планирование вызова этой функции, например, путём добавления её в качестве обработчика onClick, или её вызов, путём обращения к this.props.whateverTheFunctionIsCalled. Это приводит к вызову функции, находящейся в родительском компоненте. Именно этот процесс описан в разделе об удалении элементов из списка.

▍Vue

При использовании Vue, в дочернем компоненте достаточно написать функцию, которая передаёт данные родительской функции. В родительском компоненте пишется функция, которая прослушивает события передачи значения. При возникновении подобного события такая функция вызывается. Как и в случае с React, описание этого процесса можно найти в разделе об удалении элементов из списка.

Итоги

Мы поговорили о том, как добавлять, удалять и изменять данные в приложениях, основанных на Vue и React, о том, как передавать данные, в виде свойств, от родительских компонентов дочерним, и как отправлять данные от дочерних компонентов родительским. Полагаем, что после анализа примеров сходства и различия Vue и React видны, что называется, невооружённым глазом.

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

Уважаемые читатели! Какие различия между React и Vue, на ваш взгляд, являются самыми главными, влияющими на выбор того или иного фреймворка, например, в качестве основы для некоего проекта?

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть