image Здравствуйте, коллеги. Мы возобновляем наши переводные публикации. Сегодняшний текст анонсирует давно назревшую новинку по веб-разработке, посвященную ультрасовременной библиотеке Vue.js. Учитывая, что у нас в ассортименте имеется сразу три отличные книги по React, а также книга по GraphQL, эта книга, несомненно, составит им хорошую компанию. О сильных сторонах Vue по сравнению с React – читайте под катом.

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

Конечно, неэффективно распыляться между схожими инструментами, но разве вам не любопытно? Мне – любопытно.

В интернете найдется множество сравнительных статей о том, как создать приложение в жанре «список дел» или т.п. с Vue и React, но реальные проекты редко бывают настолько просты. В реальном приложении приходится позаботиться о маршрутизации, сохранении состояния, совместимости плагинов, т.д.

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

Приложения


Я использую Vue уже около двух лет, а веб-разработкой занимаюсь лет восемь. Впервые попробовав силы с Vue, я решил, что буду учить ее «в открытую», выложив в опенсорс простое приложение для ведения заметок, где будет возможность аутентификации пользователя при помощи JWT, а также полный набор CRUD-действий с заметками. В пару к нему я писал бэкендовое приложение, сделанное с использованием Koa.

Хотя, я и не испытывал острой необходимости менять фреймворк, я рассудил, что было бы неплохо выучить React. Поэтому я переделал на React мое приложение koa-vue-notes и также выложил его в опенсорс. Подумал, что такой опыт как минимум расширит мои представления о JavaScript, а, может быть, и найду себе новый любимый инструмент.

Вот домашняя страница моего приложения. Сверху – вариант с React, снизу – с Vue:

Хотя, я использую Bootstrap в моих приложениях все реже, обычно я внедряю в мои приложения новый компонент Navbar, появившийся в Bootstrap 4. Пытаясь повторить такое в Vue, я обнаружил, что Bootstrap-Vue – наилучший вариант для реализации Bootstrap 4. В React мои опыты и исследования привели меня к reactstrap.

В данном случае нужно отметить, что в конечном итоге я не стал использовать в React сетку Bootstrap, а остановился на варианте grid-styled, лучше сочетавшемся с применяемыми у меня styled-components – подробнее об этом ниже.

В приложении можно осуществлять с пользователем операции signup/login/forgot/reset, а с его заметками — create/read/edit/delete. Войдите в систему под demousername и demopassword, если лень регистрироваться.

Сравнение каталогов с исходным кодом


Первые впечатления


При работе с React сразу же становится очевидна одна вещь: придется очень плотно иметь дело с JavaScript.

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

Сравнение React-Router и Vue-Router


React-Router – это активно применяемая система маршрутизации для React. Скорость у нее отличная, однако, имея с ней дело на практике, я сталкивался с некоторыми интересными проблемами. Базовая настройка совсем простая, хотя, я не фанат объявления маршрутов прямо в HTML, как это требуется делать в React-Router v4 (в более ранних версиях React-Router ситуация была иная).

Продолжая препарировать мои маршруты, я повстречал такую проблему: как не допустить пользователей на те страницы, к которым у них не должно быть доступа. Элементарный пример: пользователь пытается открыть страницу типа account, не выполнив вход в систему. Потребовалось потратить не один час на изучение ситуации и действия методом проб и ошибок, чтобы выдать окончательное решение с применением React-Router.

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

image

Vue-Router – это собственная библиотека Vue, предназначенная для маршрутизации. Мне в самом деле нравится, как там можно добавлять дополнительную информацию к определениям маршрутов прямо в файле с объявлением маршрута. Посмотрите, как я закрыл пользователям доступ к страницам в Vue-Router при помощи свойства requiresAuth в определении маршрута и проверил истинность в функции router.beforeEach:

image

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

Далее, когда я пытался собирать по URL некоторые данные со страницы Edit, я обнаружил, что в самой свежей версии React-Router такая возможность оказалась удалена. Меня это… разочаровало. Думаю, я понимаю, зачем это было сделано: данные в строке запроса поступают во всевозможных видах и формах, но, позвольте, если даже параметр по URL взять невозможно – это как-то чересчур. Мне пришлось скачать библиотеку qs, чтобы правильно разбирать URL, и в ней также нашлись свои процедурные причуды. Подробное обсуждение – здесь.

На все про все, чтобы во всем разобраться, я потратил лишний час. Не самая серьезная проблема, однако, она разительно отличается от того опыта, что мне выпал с Vue-Router, а именно: поищи в документации и реализуй решение в коде. Я не пытаюсь сказать, что с Vue – не жизнь, а сказка; просто в случае с React у меня сложилось впечатление, как будто путь выдался заметно тернистее, нежели я ожидал.

Сравнение Redux и Vuex


Redux – это самое популярное хранилище данных в React, построенное по шаблону Flux. Если Flux вам не известен, поясню: это паттерн проектирования, в целом, основанный на однонаправленном потоке данных, организуемом путем диспетчеризации действий изнутри приложения. Иными словами, он все поддерживает в порядке, когда вы пытаетесь обращаться к данным из всевозможных ваших компонентов и манипулировать этими компонентами.
Вот для примера файлы из нашего хранилища Redux, где мы создаем запись при помощи actions и reducer:

image

В принципе, идея такова: мы диспетчируем actions для срабатывания reducers, которые безопасно манипулируют данными из хранилища. Таким образом, каждый компонент может безопасно считывать данные и реагировать на изменения в них.

Vuex в мире Vue эквивалентен Redux. Обе библиотеки обладают в этой сфере по-настоящему великолепной собственной поддержкой. Вместо reducers Vuex использует mutations для безопасного обновления данных хранилища. Кроме небольших отличий в именовании, обе библиотеки очень похожи. Ниже я реализовал ту же самую функциональность в приложении Vue в src/store/note.js (естественно, оба примера немного сокращены):

image

Честно говоря, Redux показалась мне полезной и мощной библиотекой-хранилищем для React, вдохновленной принципом Flux. У меня возникли проблемы из-за лишнего стереотипного кода. Естественно, теперь, когда я во всем разобрался, все кажется простым и ясным, но опыт подсказывает, что новичку в обращении с Redux будет сложно реализовать четкий и лаконичный код для React.

Например, приходится изучать и устанавливать библиотеку redux-thunk, чтобы диспетчировать действия от других действий, и для меня это был неприятный поворот. Конечно, я потратил еще пару часов, размышляя, а не воспользоваться ли redux-saga или redux-observable вместо redux-thunk. Вот тогда у меня мозги заскрипели, ощущение как раз можно описать словом thunk.

Это была сквозная тема данного проекта. Вспомните для сравнения интеграцию с Vuex – я, например, помню, как ловил себя на мысли «неужели это со мной?», налаживая все это впервые – а я к тому моменту еще даже не успел познакомиться с паттерном проектирования Flux.

Рендеринг


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

Во Vue, если вы желаете что-либо отобразить или скрыть, просто сделайте так:

image

и этот код будет зависеть от истинности вашей myVariable. В React, по-видимому, приходится сделать так:

image

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

Styled-Components


Знаете, что мне больше всего нравится в этом проекте? Styled-Components. Мне по-настоящему импонирует инкапсуляция, которую они обеспечивают. Да, во Vue можно приколоть свойство scoped в разделе вашего компонента и, в принципе, сделать то же самое.

Было что-то действительно гладкое и приятное в том, как каждый компонент превращается в маленькую «вещь в себе». Возникают небольшие сложности с передачей произвольных свойств (props), но, уладив с ними некоторые детали, работать стало приятно. Помню один пользовательский комментарий, отлично ухвативший эту мысль: «приучает тебя заранее продумывать, как будешь оформлять компоненты».

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

Сравниваем Create-React-App и Vue-CLI


Мне действительно понравилось create-react-app. Притом, какой я фанат vue-cli, вариант create-react-app – его достойный конкурент. Рекомендую всем пользователям установить экземпляр Webpack с нуля, чтобы разобраться в деталях. Но, если вам требуется что-то солидное для продакшена, настоятельно рекомендую использовать готовые инструменты скаффолдинга.

Инструменты разработки


Также отмечу: инструменты разработки в Redux и React определенно не так хороши, как инструменты Vue, это касается как оформления и цвета, так и необходимости раскрывать гигантское дерево компонентов, просто чтобы посмотреть состояние компонента. Мне в таком режиме было довольно тяжело следить за переменными приложения.

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

Заключение


При прочих равных, я очень рад, что потратил время на изучение React. Знаю, я по-прежнему криворук и в работе с ним, и в программировании вообще, но, как минимум, теперь я освоил некоторые сложные вещи и познакомился с концепциями. Также я планирую попробовать React Native, на случай, если в будущем придется заняться разработкой мобильных приложений. Такой опыт точно не помешает.

Я мог бы бесконечно вдаваться в сравнение мелочей. На самом деле, эта статья – лишь малая толика того, что можно сказать о сравнении Vue/React. Поэкспериментируйте с приложением – мне его активно комментировали, и эти советы и подсказки пригодятся и вам.

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

» Ссылка на предзаказ книги

Бумажная версия появится в конце марта.

Для Хаброжителей скидка 25% по купону — Vue.js

Комментарии (25)


  1. iit
    01.03.2019 15:46
    -2

    Для React аналог Vuex это не разу не Redux а скорее Mobx а точнее MobxStateTree.


    example

    mobx-state-tree


    const Todo = types
        .model({
            text: types.string,
            completed: false,
            id: types.identifierNumber
        })
        .actions(self => ({
            remove() {
                getRoot(self).removeTodo(self)
            },
            edit(text) {
                self.text = text
            },
            complete() {
                self.completed = !self.completed
            }
        }))

    Vuex


    export default new Vuex.Store({
     state: {
       text: '',
       completed: false,
       id: null,    
     },
     mutations: {
        edit(store,text) {
            store.text = text
        },
        complete(store) {
            store.completed = !store.completed
        }
     },
     actions: {
        remove({dispatch, store}){
            dispatch('todos/remove', store.id, { root: true});
        },
    });


  1. questor
    01.03.2019 15:54

    Попробовал открыть оглавление книги, чтобы посмотреть заголовок тем, но увы:

    The requested URL /upload/contents/978544611098/978544611098_X.pdf was not found on this server.


    Можете рассказать вкратце, что по сравнению с официальным руководством будет сверх в книге? На 304 можно и воды налить, разбавив оф. гайд, а можно и достаточно интересные подробности рассказать.


  1. TheShock
    01.03.2019 16:51
    +1

    В оригинале код вставлен текстом, а вы его вставили скриншотами. Вы, блин, издеваетесь? Это же код. Банально, если хочешь прокомментировать статью — ты не можешь вставить адекватную цитату. Что за идиотская мода на скриншоты кода вместо кода?


  1. dmitryrf
    01.03.2019 18:14

    Вот домашняя страница моего приложения. Сверху – вариант с React, снизу – с Vue:

    Сравнение каталогов с исходным кодом


    Отсутствуют иллюстрации из оригинальной статьи.


  1. vintage
    01.03.2019 18:49

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


    Далее, в вуешном роутере зачем-то для каждого роута указывается ещё и заголовок страницы. Зачем это роутеру? В заголовке страницы пользователь ожидает увидеть название заметки, а не абстрактный "Note". И опять же, указывается компоннет без параметров. Кастомные мета-поля — это, конечно, замечательно, но что насчёт статической типизации и подсказок IDE? Одна опечатка и проверка авторизации отваливается.


    Теперь роутинг здорового человека:


    pages() {
        return [
            this.Menu() ,
            ... ( this.page_id() === 'note' ) ? this.pages_note() : []
        ]
    }
    
    pages_note() {
        if( this.action_id() === 'create' ) return this.pages_note_create()
        if( this.action_id() === 'edit' ) return this.pages_note_edit()
        return [ this.Node_view() ]
    }
    
    pages_note_create() {
        return this.profile()
        ? [ this.Note_create() ]
        : [ this.Auth() ]
    }
    
    pages_note_edit() {
        return this.note().author() === this.profile()
        ? [ this.Note_edit() ]
        : [ this.Auth() ]
    }

    Так как инициализация и композиция разделены, то нам достаточно лишь указать "тут должен быть такой-то вложенный компонент". Если пользователь не авторизован или не является автором заметки, то показываем форму авторизации.


    1. TheShock
      01.03.2019 20:04

      То, что вы привели — очень похоже на God Object, но в Реакте роутинг — отвратительный, тут сомнений нет.


      1. vintage
        01.03.2019 23:44

        Где ж вы тут божественный объект углядели?


        1. TheShock
          02.03.2019 02:19

          Сильно много ответственности. Тут и роутинг и бизнес-логика для каждого роута.


          1. vintage
            02.03.2019 09:08

            Роутинг — это и есть «бизнес-логика», определяющая какие экраны показывать в какой момент времени.


            1. TheShock
              02.03.2019 15:00

              какие экраны показывать в какой момент времени.

              Какие экраны зависимо от пути. Зависимость от других переменных — это уже не роутинг.

              Но в вашем примере оно хоть в правильном месте лежит — в клиентской модели, а не во вьюшки, как это сделано в Реакте.


              1. vintage
                02.03.2019 16:04

                Не важно как это называть, суть-то одна — какие экраны показывать в зависимости от разных состояний.

                Да нет, это вполне себе вьюшка, решающая какие вьюшки показывать внутри.


              1. VolCh
                04.03.2019 10:04

                Мне нравится подход, когда изменение URL лишь вызывает изменения в каком-то стейте, а роутинг — чистая функция, принимающая стейт и возвращающая готовый элемент.


                1. TheShock
                  04.03.2019 11:13

                  Я вообще стараюсь абстрагировать от пути в браузере. Допустим статья у меня находится по адресу /article/123.

                  Для неё есть роут (далее псевдокод):

                  class ArticleRoute extends Route {
                    readonly template = '/article/{id}';
                  
                    constructor (readonly id: number) {}
                  }


                  Когда открывается страница браузера или меняется путь — роутер (который находится в слое модели) просчитывает, какой роут больше всего подходит. В данном случае в роутер будет присвоено что-то вроде такого:

                  router.route = new ArticleRoute(123)


                  Это, естественно, MobX-observable свойство, которое при изменении автоматически перерисует все зависимые вьюшки.

                  Ссылки на странице я указываю не как
                  <a href={`/article/${target.id}`}>
                  

                  а как
                  <Link route={new ArticleRoute(target.id)}>

                  а если надо вручную переместить пользователя куда-то, то делаю не
                  window.location = `/article/${target.id}`;
                  

                  а
                  router.move(new ArticleRoute(target.id))


                  Мне нравится абстрагированность от путей (везде кроме маппинга) и статическая типизация во всём этом. А еще, при необходимости, можно узнать текущий роут в любом компоненте без костылей, ведь он — часть стейта, как и все остальные данные.

                  А вьюшка рендерится как-то так:

                  const RouteToViewMapping = {
                    [ArticleRoute]: ArticleView,
                    // ....
                  }
                  
                  function renderRouteToView(route) {
                    const Component = RouteToViewMapping[route.constructor];
                    return <Component route={route} />;
                  }


        1. DarthVictor
          02.03.2019 15:26

          На каждый роут в приложении — свой метод одного и того же объекта. При 30 роутах у объекта будет 30 методов?
          Если уж начали про роутинг здорового человека писать, то укажу, пожалуй universal-router.


          1. vintage
            02.03.2019 16:26

            Если у вас на одном уровне иерархии 30 вариантов вложенных компонент, то будет 30 методов, да.


  1. akeinhell
    02.03.2019 07:37

    Сразу видно человек не пишет на реакте и сравнивает с ним свой любимый vue

    На счет реакт-роутера есть очень логичное решение.
    делаете HOC, который инкапсулирует в себя всю логику с проверками на авторизацию, ACL и т.д.

    А потом очень просто это все подключаем

        <Route path="/" exact component={PostList} />
        <Route path="/edit/:id" component={WithACL(PostEdit, {allow=['admin', 'author']})}  />
        <Route path="/delete/:id" component={WithACL(PostDelete, {allow=['admin']})}  />
        <Route path="/create/" component={WithACL(PostCreate, {allow=['author']})} />
    


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


    1. nuit
      02.03.2019 08:46

      > Сразу видно человек не пишет на реакте и сравнивает с ним свой любимый vue

      Тут даже хуже, человек не владеет жаваскриптом, тк для композиции в реакте не нужно изучать реакт.


    1. strannik_k
      02.03.2019 13:21

      Давно не работал с реакт роутером, но вместо HOC

      <Route path="/edit/:id" component={WithACL(PostEdit, {allow=['admin', 'author']})}  />
      

      своя обертка над Route, на мой взгляд, будет немного получше:
      <CustomRoute path="/edit/:id" component={PostEdit} allow={['admin', 'author'])}  />



      1. TheShock
        02.03.2019 14:56

        А как два HOC'а замените? Это ведь классические композиция-против-наследования.


        1. strannik_k
          02.03.2019 17:17

          Я имел ввиду не наследование CustomRoute от Route, а рендерить Route внутри CustomRoute.
          Сейчас понял, что фактически предложил HOC, но для Route, а не для PostEdit.


  1. vlviking
    02.03.2019 09:32

    А мне, чем Vue больше нравится, так это Enter/Leave & List Transitions. Очень удобно работать с простыми анимациями. В реакт сложнее.


  1. sshmakov
    02.03.2019 09:54

    Может я что не понял — человек ограничения доступа неавторизованным пользователям реализует на фронте? Серьезно?


    1. TheShock
      02.03.2019 12:18

      Ну это нормально. Надо выдать вменяемую страницу, а не просто админский шаблон с незагруженными из-за 403 данными. Просто на сервере это тоже реализуется


      1. vintage
        02.03.2019 13:42
        +1

        Есть мнение, что такой шаблон надо показывать не в зависимости от роутов, а если от сервера пришёл 403 ответ. Тогда логика проверки прав будет в одном месте, а не размазана между фронтом и бэком, с периодическим разъездами то в одну, то в другую сторону.


        1. TheShock
          02.03.2019 14:55

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

          Другой пример — валидация полей форм, которую нужно делать на сервере и можно сделать исключительно там, но от этого страдает ux.