Обзор Vue.js 2.6

    Привет, Хабр!

    Совсем скоро должна выйти новая версия Vue.js — 2.6. Под катом вы найдете обзор новых фич следующей версии, включая новый синтаксис слотов, Vue.observable() и много чего еще!


    1. Новый синтаксис для scoped slots


    Это одно из самых значимых изменений. Оно включает в себя:

    • Новую директиву v-slot, объединяющую slot и slot-scope
    • Сокращение для использования именованных scoped slots

    Проще всего это понять на примере:

    Как использовались scoped slots в версии Vue@2.5.22:

    <template>
      <TestComponent>
        <!-- Дефолтный scoped slot -->
        <div slot-scope="{ message }">
          {{ `Default slot with message: ${message}` }}
        </div>
    
        <!-- Именованный scoped slot -->
        <div slot="text" slot-scope="{ text }">
          {{ `Named slot with text: ${text}` }}
        </div>
      </TestComponent>
    </template>
    

    Как можно теперь:

    <template>
      <TestComponent>
        <!-- Дефолтный scoped slot -->
        <template v-slot="{ message }">
          <div>
            {{ `Default slot with message: ${message}` }}
          </div>
        </template>
    
        <!-- Именованный scoped slot -->
        <template v-slot:text="{ text }">
          <div>
            {{ `Named slot with text: ${text}` }}
          </div>
        </template>
      </TestComponent>
    </template>
    

    Для дефолтного слота можно применить специальный синтаксис, если не используются именованные слоты:

    <template>
      <!-- v-slot используется прямо на родителе -->
      <TestComponent v-slot="{ message }">
        <div>
          {{ `Default slot with message: ${message}` }}
        </div>
      </TestComponent>
    </template>
    

    И вот сокращение для именованных слотов:

    <template>
      <TestComponent>
        <!-- # - это сокращение для v-slot: -->
        <template #text="{ text }">
          <div>
            {{ `Named slot with text: ${text}` }}
          </div>
        </template>
      </TestComponent>
    </template>
    

    Новую директиву можно использовать и без каких-либо scope-переменных, но тогда слот все-равно попадет в $scopedSlots родителя.

    Ссылки:

    1. Новый синтаксис v-slot

    2. Сокращение для v-slot


    2. Динамический аргумент директивы


    Если вы хотите динамический аргумент для v-bind или v-on, то во Vue@2.5.22 у вас есть только один вариант:

    <div v-bind="{ [key]: value }"></div>
    <div v-on="{ [event]: handler }"></div>
    

    Но у него есть пара недостатков:

    • Не все знают о возможности использования v-bind/v-on на объектах и о динамических названиях переменных
    • vue-template-compier генерирует неэффективный код
    • v-slot не имеет похожего синтаксиса для объектов

    Чтобы избавиться от них, Vue@2.6.0 представляет новый синтаксис:

    <div v-bind:[key]="value"></div>
    <div v-on:[event]="handler"></div>
     

    Примеры использования:

    <template>
      <div>
        <!-- v-bind с динамическим ключом -->
        <div v-bind:[key]="value"></div>
    
        <!-- сокращение v-bind с динамическим ключом  -->
        <div :[key]="value"></div>
    
        <!-- v-on с динамическим событием -->
        <div v-on:[event]="handler"></div>
    
        <!-- сокращение v-on с динамическим событием -->
        <div @[event]="handler"></div>
    
        <!-- v-slot с динамическим именем -->
        <TestComponent>
          <template v-slot:[name]>
            Hello
          </template>
        </TestComponent>
    
        <!-- сокращение v-slot с динамическим именем -->
        <TestComponent>
          <template #[name]>
            Cool slot
          </template>
        </TestComponent>
      </div>
    </template>
    

    Ссылки:


    3. Создание реактивных объектов с помощью Vue.observable()


    Раньше, чтобы создать реактивный объект, нужно было засунуть его внутрь инстанса vue-компонента. Теперь у нас есть отдельный метод, который делает объект реактивным — Vue.observable().

    Реактивный объект можно спокойно использовать в render- и computed-функциях.

    Пример использования:

    import Vue from vue;
    
    const state = Vue.observable({
      counter: 0,
    });
    
    export default {
      render() {
        return (
          <div>
            {state.counter}
    
            <button v-on:click={() => { state.counter++; }}>
              Increment counter
            </button>
          </div>
        );
      },
    };
    

    4. Загрузка данных на сервере


    В новом обновлении vue-server-renderer изменил стратегию загрузки данных для SSR.

    Раньше нам советовали вызывать методы asyncData() у компонентов, полученных через router.getMatchedComponents().

    В новой версии появился специальный метод у компонентов — serverPrefetch(). vue-server-renderer вызовет его у каждого компонента и дождется решения возвращенных промисов:

    <template>
      <div v-if="item">
        {{ item.name }}
      </div>
    </template>
    
    <script>
    export default {
      // Вызовется на сервере
      async serverPrefetch() {
        await this.fetchItem();
      },
    
      computed: {
        item() {
          return this.$store.state.item;
        },
      },
    
      // Вызовется на клиенте
      mounted() {
        if (!this.item) {
          this.fetchItem();
        }
      },
    
      methods: {
        async fetchItem() {
          await this.$store.dispatch('fetchItem');
        },
      },
    };
    </script>
    

    Чтобы узнать, когда завершилось ожидание всех serverPrefetch() и приложение завершило свой рендеринг, в контексте функции серверного рендера появилась возможность добавить хук rendered():

    /* Упрощенный entry-server.js */
    
    import { createApp } from './app';
    
    export default context => new Promise((resolve, reject) => {
      const { app, router, store } = createApp();
      const { url } = context;
    
      router.push(url);
    
      router.onReady(() => {
        context.rendered = () => {
          // Передаем состояние хранилища после завершения всех serverPrefetch()
          context.state = store.state;
        };
    
        resolve(app);
      }, reject);
    });
    

    5. Улучшенный вывод ошибок компилятора


    При компиляции html в render-функцию vue-template-compiler может выдать ошибки. Раньше Vue выводил описание ошибки без ее местоположения, теперь новая версия будет показывать, где она находится.

    Пример:

    <template>
      <div>
        <template key="test-key">
          {{ message }}
        </template>
      </div>
    </template>
    

    Ошибка vue-template-compiler@2.5.22:

      Error compiling template:
    
      <div>
        <template key="test-key">
          {{ message }}
        </template>
      </div>
    
      - <template> cannot be keyed. Place the key on real elements instead.
    

    Новый вывод ошибки vue-template-compiler@2.6.0:

      Errors compiling template:
    
      <template> cannot be keyed. Place the key on real elements instead.
    
      1  |
      2  |  <div>
      3  |    <template key="test-key">
         |              ^^^^^^^^^^^^^^
      4  |      {{ message }}
      5  |    </template>
    

    6. Отлов асинхронных ошибок


    Теперь Vue может ловить даже асинхронные ошибки в хуках жизненного цикла и обработчиках событий.

    Пример:

    /* TestComponent.vue */
    
    <template>
      <div @click="doSomething()">
        Some message
      </div>
    </template>
    
    <script>
    export default {
      methods: {
        async doSomething() {
          await this.$nextTick();
    
          throw new Error('Another Error');
        },
      },
    
      async mounted() {
        await this.$nextTick();
    
        throw new Error('Some Error');
      },
    };
    </script>
    

    Ошибка после маунта:

    [Vue warn]: Error in mounted hook (Promise/async): "Error: Some Error"
    

    Ошибка после клика:

    [Vue warn]: Error in v-on handler (Promise/async): "Error: Another Error"
    

    7. Новая сборка для ESM браузеров


    В новой версии добавилась еще одна сборка Vue — vue.esm.browser.js. Она предназначена для браузеров, поддерживающих ES6 Modules.

    Ее особенности:

    • Содержит компилятор HTML в render-функцию
    • Использует синтаксис ES6 Modules
    • Содержит нетранспилированный код

    Пример использования:

    <html lang="en">
      <head>
        <title>Document</title>
      </head>
      <body>
        <div id="app">
          {{ message }}
        </div>
    
        <script type="module">
          // Раньше приходилось использовать vue.esm.js, 
          // который содержал транспилированный код,
          // весил чуть больше и работал чуть медленнее
          import Vue from 'path/to/vue.esm.browser.js';
    
          new Vue({
            el: '#app',
    
            data() {
              return {
                message: 'Hello World!',
              };
            },
          });
        </script>
      </body>
    </html>
    

    Если честно, мне бы хотелось видеть еще одну сборку — такую же, как vue.esm.browser.js, но без компилятора HTML. Тогда я бы смог подвозить браузерам с ES6 Modules более свежий код, когда компилирую шаблоны при сборке.

    8. Сокращение для v-bind.prop


    У директивы v-bind есть специальный модификатор — .prop. Посмотреть, что он делает, можно вот тут в документации. Сам я ни разу его не использовал и не представляю себе случай, когда его стоит применить.

    Для него теперь есть специальное сокращение: вместо записи v-bind:someProperty.prop=«foo» можно писать .someProperty=«foo».

    Пример:

    Как было во Vue@2.5.22:

    <template>
      <div>
        <div v-bind:textContent.prop="'Important text content'" />
    
        <!-- Или сокращенный вариант -->
        <div :textContent.prop="'Important text content'" />
      </div>
    </template>
    

    Как можно во Vue@2.6.0:

    <template>
      <div .textContent="'Important text content'" />
    </template>
    

    9. Поддержка кастомного toString()


    Тут все просто: если вы переопределите метод toString() у объекта, то Vue при отображении станет использовать его вместо JSON.stringify().

    Пример:

    /* TestComponent.vue */
    
    <template>
      <div>
        {{ message }}
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          message: {
            value: 'qwerty',
    
            toString() {
              return 'Hello Habr!';
            },
          },
        };
      },
    };
    </script>
    

    В версии Vue@2.5.22 мы увидим на экране:

    { "value": "qwerty" }
    

    В версии Vue@2.6.0:

    Hello Habr!
    

    10. Работа v-for с итерируемыми объектами


    В новой версии v-for может работать с любыми объектами, которые реализуют iterable protocol, например Map или Set. Правда, для Map и Set в версии 2.X не будет поддерживаться реактивность.

    Пример:

    /* TestComponent.vue */
    
    <template>
      <div>
        <div 
          v-for="item in items"
          :key="item"
        >
          {{ item }}
        </div>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          items: new Set([4, 2, 6]),
        };
      },
    };
    </script>
    


    Посмотреть все новые фишки в деле можно прямо сейчас, установив бета-версию Vue:
    npm i vue@2.6.0-beta.3
    

    Если вы компилируете vue-файлы при сборке или используете SSR, то не забудьте добавить vue-template-compiler и vue-server-renderer такой же версии:
    npm i vue-template-compiler@2.6.0-beta.3 vue-server-renderer@2.6.0-beta.3
    


    Обновление: Vue 2.6 вышел официально, тут можно почитать пост Евана.

    Спасибо, что дочитали статью до конца!
    Constanta
    32,00
    Разработка букмекерских продуктов
    Поддержать автора
    Поделиться публикацией

    Похожие публикации

    Комментарии 15

      +5
      Vue.observable()

      Т.е. теперь в Vue можно как в MobX или Knockout? Наконец то!

        0

        Angular 2+ еще

          0
          А что, разве в Angular завезли реактивность в стиле observable / computed?
            0

            Там используется RxJS.

              +1
              ...API которой похоже на API MobX, Knockout или Vue только одним словом observable :-)
        –5
        круто, спасибо за статью, плюсануть не могу к сожалению, все хорошеет библиотечка, это радует.
          0
          Немного странная логика «serverPrefetch()». Код выполняется на сервере, но при этом участок кода с этим хуком всё-равно попадает с билд для браузера.
            0
            Да, но в реальных проектах вряд ли кто-то станет прямо писать serverPrefetch() внутри компонента.

            В примере кода, который я привел, логика загрузки повторяется два раза, в serverPrefetch() и mounted():
            export default {
              /* ... */
            
              async serverPrefetch() {
                await this.fetchItem();
              },
            
              mounted() {
                if (!this.item) {
                  this.fetchItem();
                }
              },
            };
            

            В некоторых компонентах понадобится писать похожую логику и в хуке updated().

            Поэтому очевидное решение — своя абстракция над serverPrefetch() и mounted() через миксин или плагин.
              +1
              Ну, изначально суть идеи была в том, чтоб на сервере обращаться напрямую к модели через ORM, а не выполнять http-запросы для получения данных. По этому подумал что метод, выполняемый только на сервере должен выпилиться при сборке из клиенсткой части.
                0
                Хмм, грязная идея, мне нравится.

                Тогда можно написать свой babel-плагин, который выпилит все методы serverPrefetch() из экспортированных объектов во vue-файлах.
                  0
                  Это уже костыли получатся, если не задумано разработчиком Vue и не будет поддерживаться в будущем :)

                  И тут есть ещё один гвоздь, собственно, для чего и затеивался серверный рендеринг — исполнение одних и тех-же компонентов как на клиенте, так и на сервере. Это не будет иметь нужного эффекта, если придётся поддерживать 2 разных интерфейса — для клиента и для сервера. Решением возможно могла бы послужить какая-нибудь универсальная ORM, у которой всё это под капотом, но это уже совсем другая история.
            0
            npm i vue@2.6.0-beta.2


            Нужно ещё обновить vue-server-renderer и vue-template-compiler до соответствующей версии.
              0
              Спасибо, обновил статью
              0

              С такими темпами добавления нового синтаксиса в шаблонах скоро не останется валидных способов определения HTML атрибутов

                +1
                Все больше становится похож на angular

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое