Используя Vue на моем текущем рабочем месте, я имел довольно твердое представление о том, как все это работает. Однако мне было любопытно узнать, на что похожа трава по ту сторону забора - трава в этом сценарии - это React.
Я читал документацию по React и смотрел несколько обучающих видео, и, хотя они были великолепны и все такое, мне действительно хотелось узнать, чем React отличается от Vue.
Под «разными» я не имел в виду такие вещи, как наличие виртуальных DOMS у них обоих или их рендеринга страниц. Я хотел, чтобы кто-нибудь нашел время, чтобы объяснить код и рассказать мне, что происходит! Я хотел найти статью, которая потратила время на объяснение этих различий, чтобы кто-то, кто плохо знаком с Vue или React (или веб-разработкой в целом), мог лучше понять о различиях между ними.
Но я не мог найти ничего, что бы решило эту проблему. Итак, я пришел к выводу, что мне нужно пойти дальше и построить его самому, чтобы увидеть сходства и различия. Поступая так, я подумал, что задокументирую весь процесс, чтобы наконец появилась статья об этом.

Я решил попробовать создать довольно стандартное приложение To Do, которое позволяет пользователю добавлять и удалять элементы из списка. Оба приложения были созданы с использованием интерфейса командной строки по умолчанию (create-response-app для React и vue-cli для Vue). Между прочим, CLI означает интерфейс командной строки. 🤓
В любом случае, это вступление уже длиннее, чем я ожидал. Итак, давайте начнем с быстрого взгляда на то, как выглядят два приложения:

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

Вы увидите, что их структуры также почти идентичны. Единственная разница в том, что в приложении React есть три файла CSS, а в приложении Vue их нет. Это связано с тем, что в приложении create-react-app компонент React будет иметь сопроводительный файл для хранения его стилей, тогда как Vue CLI использует всеобъемлющий подход, при котором стили объявляются внутри фактического файла компонента .
В конечном счете, они оба достигают одного и того же, и нет ничего, чтобы сказать, что вы не можете пойти дальше и по-разному структурировать свой CSS в React или Vue. На самом деле все сводится к личным предпочтениям - вы услышите много дискуссий от сообщества разработчиков о том, как следует структурировать CSS. А пока мы просто будем следовать структуре, изложенной в обоих интерфейсах командной строки.
Но прежде чем мы пойдем дальше, давайте кратко рассмотрим, как выглядят типичный компонент Vue и компонент React:

Теперь это не так, давайте перейдем к мельчайшим деталям!
Как мы изменяем данные?
Но сначала, что мы вообще подразумеваем под «изменением данных»? Звучит немного технически, не так ли? По сути, это просто изменение данных, которые мы сохранили. Поэтому, если бы мы хотели изменить значение имени человека с Джона на Марк, мы бы «изменили данные».
В этом заключается ключевое различие между React и Vue. В то время как Vue по сути создает объект данных, данные в котором можно свободно обновлять, React создает объект состояния, где требуется немного больше работы для выполнения обновлений.
Теперь React реализует дополнительную работу по уважительной причине, и мы поговорим об этом чуть позже. Но сначала давайте взглянем на объект data из Vue и объект state из React:


Итак, вы можете видеть, что мы передали одни и те же данные в оба, но они просто помечены по-разному. Таким образом, передача исходных данных в наши компоненты очень и очень похожа. Но, как мы уже упоминали, способы изменения этих данных в разных фреймворках различаются.
Допустим, у нас есть элемент данных с именем name: ‘Sunil’.
В Vue мы ссылаемся на это, вызывая this.name. Мы также можем обновить это, вызвав this.name = ‘John’. Это изменит мое имя на Джон. Не знаю, как я отношусь к тому, что меня называют Джоном, но, эй-хо, бывает! 😅
В React мы могли бы ссылаться на тот же фрагмент данных, вызывая this.state.name. Ключевое отличие здесь в том, что мы не можем просто написать this.state.name = ‘John’, потому что React имеет ограничения, предотвращающие такого рода легкое и беззаботное создание мутаций. Итак, в React мы бы написали что-то вроде this.setState({name: ‘John’}).
Хотя это, по сути, делает то же самое, что мы достигли в Vue, есть дополнительный бит записи, потому что Vue по существу объединяет свою собственную версию setState по умолчанию всякий раз, когда обновляется часть данных.
Короче говоря, React требует setState, а затем обновленных данных внутри него, тогда как Vue предполагает, что вы захотите сделать это, если бы вы обновляли значения внутри объекта данных.
Почему React вообще этим занимается и зачем вообще нужен setState? Давайте передадим это Реванту Кумару для объяснения:
«Это потому, что React хочет повторно запускать определенные перехватчики жизненного цикла, [такие как] componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate при изменении состояния. Он будет знать, что состояние изменилось, когда вы вызываете функцию setState. Если вы напрямую мутируете состояние, React придется проделать гораздо больше работы, чтобы отслеживать изменения, какие хуки жизненного цикла запускать и т. Д. Поэтому, чтобы упростить работу, React использует setState ».

Теперь, когда у нас есть мутации, давайте перейдем к мелочам, посмотрев, как мы будем добавлять новые элементы в оба наших To Do Apps.
Как мы создаем новые задачи?
Реагировать:
createNewToDoItem = () => {
this.setState( ({ list, todo }) => ({
list: [
...list,
{
todo
}
],
todo: ''
})
);
};
Как React это сделал?
В React в нашем поле ввода есть атрибут под названием значение. Это значение автоматически обновляется с помощью пары функций, которые связаны вместе, чтобы создать нечто, очень напоминающее двустороннюю привязку (если вы никогда не слышали об этом раньше, есть более подробное объяснение в разделе Как Vue сделал это после этого). Мы создаем эту форму двусторонней привязки, прикрепляя дополнительный прослушиватель событий onChange к полю input.
Давайте быстро взглянем на поле input, чтобы понять, что происходит:
<input type="text"
value={this.state.todo}
onChange={this.handleInput}/>
Функция handleInput запускается всякий раз, когда значение поля ввода изменяется. Он обновляет задачу, которая находится внутри объекта состояния, устанавливая для него значение, указанное в поле ввода. Эта функция выглядит так:
handleInput = e => {
this.setState({
todo: e.target.value
});
};
Теперь, когда пользователь нажимает кнопку + на странице, чтобы добавить новый элемент, функция createNewToDoItem по существу запускает this.setState и передает ему функцию.
Эта функция принимает два параметра, первый - это весь массив list из объекта состояния, второй - todo (который обновляется с помощью _10 _ функция). Затем функция возвращает новый объект, который содержит весь list из предыдущего, а затем добавляет todo в конец. Весь список добавляется с помощью оператора распространения (погуглите, если вы не видели этого раньше - это синтаксис ES6).
Наконец, мы устанавливаем todo в пустую строку, которая автоматически обновляет значение в поле input.
Vue:
createNewToDoItem() {
this.list.push(
{
'todo': this.todo
}
);
this.todo = '';
}
Как Vue это сделал?
В Vue наше поле ввода имеет дескриптор под названием v-model. Это позволяет нам делать что-то, известное как двусторонняя привязка. Давайте быстро взглянем на поле ввода, а затем объясним, что происходит:
<input type="text" v-model="todo"/>
V-Model связывает ввод этого поля с ключом, который есть в нашем объекте данных, который называется toDoItem. Когда страница загружается, в toDoItem устанавливается пустая строка, например: todo: ‘’. Если бы там уже были какие-то данные, такие как todo: ‘add some text here’, наше поле ввода загрузилось бы с добавлением текста сюда уже внутри поля ввода.
В любом случае, возвращаясь к использованию пустой строки, любой текст, который мы вводим в поле ввода, привязывается к значению todo. Это фактически двусторонняя привязка (поле ввода может обновлять объект данных, а объект данных может обновлять поле ввода).
Итак, оглядываясь на предыдущий блок кода createNewToDoItem(), мы видим, что мы помещаем содержимое todo в массив list , а затем обновите todo до пустой строки.
Как удалить из списка?
Реагировать:
deleteItem = indexToDelete => {
this.setState(({ list }) => ({
list: list.filter((toDo, index) => index !== indexToDelete)
}));
};
Как React это сделал?
Итак, хотя функция deleteItem находится внутри ToDo.js, мне было очень легко сделать ссылку на нее внутри ToDoItem.js, сначала передав deleteItem() как опора для <ToDoItem/> как такового:
<ToDoItem deleteItem={this.deleteItem.bind(this, key)}/>
Это передает функцию вниз, чтобы сделать ее доступной для ребенка. Здесь вы увидите, что мы также связываем this и передаем параметр ключа, поскольку ключ - это то, что функция будет использовать, чтобы различать, какой ToDoItem мы пытаемся удалить, когда щелкнул. Затем внутри компонента ToDoItem мы делаем следующее:
<div className=”ToDoItem-Delete” onClick={this.props.deleteItem}>-</div>
Все, что мне нужно было сделать, чтобы сослаться на функцию, которая находилась внутри родительского компонента, - это сослаться на this.props.deleteItem.
Vue:
onDeleteItem(todo){
this.list = this.list.filter(item => item !== todo);
}
Как Vue это сделал?
В Vue требуется немного другой подход. По сути, здесь мы должны сделать три вещи:
Во-первых, на элементе, который мы хотим вызвать функцию:
<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" />
Это так называемый настраиваемый прослушиватель событий. Он прислушивается к любому случаю, когда излучение запускается строкой ‘delete’. Если он слышит это, он запускает функцию с именем onDeleteItem. Эта функция находится внутри ToDo.vue, а не ToDoItem.vue. Как мы обсуждали ранее, он просто фильтрует todo массив внутри объекта data, чтобы удалить элемент, по которому был выполнен щелчок.
Здесь также стоит отметить, что в примере Vue я мог бы просто написать часть $emit внутри слушателя @click как таковая:
<div class=”ToDoItem-Delete” @click=”$emit(‘delete’, todo)”>-</div>
Это уменьшило бы количество шагов с 3 до 2, и это просто зависит от личных предпочтений.
Короче говоря, дочерние компоненты в React будут иметь доступ к родительским функциям через this.props (при условии, что вы передаете реквизиты, что является довольно стандартной практикой - вы столкнетесь с этим множество раз в других примерах React). . В Vue же, с другой стороны, вы должны генерировать события от дочернего элемента, которые обычно собираются внутри родительского компонента.
Как передать слушателей событий?
Реагировать:
Слушатели событий для простых вещей, таких как события щелчка, просты. Вот пример того, как мы создали событие щелчка для кнопки, которая создает новый элемент ToDo:
<div className=”ToDo-Add” onClick={this.createNewToDoItem}>+</div>.
Здесь очень просто, и очень похоже на то, как мы будем обрабатывать встроенный onClick с помощью vanilla JS. Как упоминалось в разделе Vue, настройка прослушивателя событий для обработки при каждом нажатии кнопки ввода заняла немного больше времени. По сути, для этого требовалось, чтобы событие onKeyPress обрабатывалось тегом ввода, как таковое:
<input type=”text” onKeyPress={this.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 для создания новых элементов ToDo при каждом нажатии кнопки ввода занимает немного больше времени. В Vue я мог просто написать:
<input type=”text” v-on:keyup.enter=”createNewToDoItem”/>
Как передать данные дочернему компоненту?
Реагировать:
В 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’.
Как мы отправляем данные обратно в родительский компонент?
Реагировать:
Сначала мы передаем функцию дочернему компоненту, ссылаясь на нее как на опору в том месте, где мы вызываем дочерний компонент. Затем мы добавляем вызов функции для дочернего элемента любыми способами, например, onClick, ссылаясь на this.props.whateverTheFunctionIsCalled. Затем это вызовет функцию, которая находится в родительском компоненте.
Мы можем увидеть пример всего этого процесса в разделе «Как удалить из списка».
Vue:
В нашем дочернем компоненте мы просто пишем функцию, которая возвращает значение родительской функции. В нашем родительском компоненте мы пишем функцию, которая прослушивает, когда это значение испускается, а затем может запускать вызов функции. Мы можем увидеть пример всего этого процесса в разделе «Как удалить из списка».
И вот оно! 🎉
Мы рассмотрели, как мы добавляем, удаляем и изменяем данные, передаем данные в виде реквизитов от родителя к потомку и отправляем данные от потомка к родителю в виде прослушивателей событий.
Конечно, между React и Vue есть множество других небольших различий и причуд, но, надеюсь, содержание этой статьи помогло послужить основой для понимания того, как обе структуры справляются со всем этим 🤓
Если вы нашли это полезным, поделитесь, прокомментируйте и оставьте много аплодисментов! 👏
Ссылки на Github на оба приложения:
Vue ToDo: https://github.com/sunil-sandhu/vue-todo
React ToDo: https://github.com/sunil-sandhu/react-todo
Это синдицированный репост для freeCodeCamp в сотрудничестве с Javascript In Plain English. Исходную версию этой статьи можно найти здесь.