Интересные новинки Vue 3

в 7:46, , рубрики: composition, javascript, vue, vuejs, Блог компании FunCorp, Программирование, Разработка веб-сайтов

Вместо предисловия

Vue используется во всех проектах FunCorp. Мы внимательно следим за развитием фреймворка, постоянно улучшаем процесс разработки и внедряем лучшие практики. И, конечно же, мы не могли пройти мимо и не перевести статью Филиппа Раковски, сооснователя VueStorefront, про новые фичи Vue 3, серьёзно влияющие на написание кода.

image
В прошлый раз мы рассматривали фичи, которые влияют на производительность Vue 3. Нам уже известно, что приложения, написанные на новой версии фреймворка, работают очень быстро, но производительность — не самое важное изменение. Для большинства разработчиков намного важнее то, как Vue 3 повлияет на стиль написания кода.

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

Давайте начнём с API, о котором многие из вас могли слышать.

Composition API

Composition API — самая обсуждаемая и упоминаемая фича следующей мажорной версии Vue. Синтаксис Composition API предоставляет абсолютно новый подход к организации и переиспользованию кода.

Сейчас мы создаём компоненты с синтаксисом, который называется Options API. Для того чтобы добавить логику, мы создаём свойства (опции) в объекте компонента, например data, methods, computed и т.д. Основным недостатком данного подхода является то, что это не JavaScript-код как таковой. Вам необходимо точно знать, какие опции доступны в шаблоне и каким будет поведение this. Компилятор Vue преобразует свойства в работающий JavaScript-код за вас. Из-за этой особенности мы не можем в полной мере пользоваться автодополнением или проверкой типов.

Composition API решает эту проблему и даёт возможность использовать механизмы, доступные через опции, с помощью обыкновенных JavaScript-функций.
Команда Vue описывает Composition API как «дополнительный, основанный на функциях API, позволяющий гибко использовать композицию в логике компонента». Код, написанный с помощью нового API, лучше читается, что делает его более лёгким для понимания.

Чтобы разобраться в том, как работает новый синтаксис, рассмотрим пример простого компонента.

<template>
  <button @click="increment">
    Count is: {{ count }}, double is {{ double }}, click to increment.
  </button>
</template>

<script>
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const double = computed(() => count.value * 2)

    function increment() {
      count.value++
    }

    onMounted(() => console.log('component mounted!'))

    return {
      count,
      double,
      increment
    }
  }
}
</script>

Разобьём код на части и разберём, что же здесь происходит.

import { ref, computed, onMounted } from 'vue'

Как я уже упоминал выше, Composition API представляет опции компонента как функции, следовательно, первым делом мы должны импортировать необходимые функции. В этом примере нам нужно создать реактивное свойство с помощью ref, вычисляемое с помощью computed и получить доступ к хуку mounted жизненного цикла с помощью функции onMounted.

Возможно, у вас возникнет вопрос: что это за таинственный метод setup?

export default {
  setup() {}
}

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

К тому, что мы не вернём из setup, доступа в шаблоне не будет.

const count = ref(0)

Реактивное свойство count инициализируем с помощью функции ref. Она принимает примитив или объект и возвращает реактивную ссылку. Переданное значение будет сохранено в свойстве value созданной ссылки. Например, если мы хотим получить доступ к значению count, нам необходимо явно обратиться к count.value.

const double = computed(() => count.value * 2)
 
function increment() {
  count.value++
}

Так мы объявляем вычисляемое свойство double и функцию increment.

onMounted(() => console.log('component mounted!'))

C помощью хука onMounted мы выводим в консоль сообщение после монтирования компонента для демонстрации такой возможности.

return {
  count,
  double,
  increment
}

Чтобы свойства count и double и метод increment были доступны в шаблоне, возвращаем их из метода setup.

<template>
  <button @click="increment">
    Count is: {{ count }}, double is {{ double }}. Click to increment.
  </button>
</template>

И вуаля! У нас есть доступ к свойствам и методам из setup, точно так же, как если бы они были объявлены через старый Options API.

Это простой пример, подобное можно было бы легко написать и с помощью Options API.
Но преимущество нового Composition API не столько в возможности писать код в другом стиле, сколько в возможностях, открываемых для повторного использования логики.

Переиспользование кода с Composition API

Давайте подробнее рассмотрим преимущества нового Composition API, например, для переиспользования кода. Сейчас, если мы хотим использовать какой-то кусок кода в нескольких компонентах, у нас есть два варианта: миксины (mixins) и слоты с ограниченной областью видимости (scoped slots). Оба варианта имеют свои недостатки.

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

Для начала рассмотрим реализацию с использованием миксинов.

import CounterMixin from './mixins/counter'
 
export default {
  mixins: [CounterMixin]
}

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

Теперь рассмотрим слоты с ограниченной областью видимости.

<template>
  <Counter v-slot="{ count, increment }">
     {{ count }}
    <button @click="increment">Increment</button> 
  </Counter> 
</template>

При использовании слотов мы в точности знаем, к каким свойствам мы имеем доступ через директиву v-slot, что достаточно просто понять. Недостаток этого подхода в том, что мы можем получить доступ только к данным компонента Counter.

А теперь рассмотрим реализацию с использованием Composition API.

function useCounter() {
  const count = ref(0)
  function increment () { count.value++ }
 
  return {
    count,
    incrememt
  }
}
 
export default {
  setup () {
    const { count, increment } = useCounter()
    return {
      count,
      increment
    }
  }
}

Выглядит гораздо элегантнее, не так ли? Мы не ограничены ни шаблоном, ни областью видимости и точно знаем, какие свойства счётчика доступны. И благодаря тому, что useCounter — просто функция, которая возвращает данные, в качестве приятного бонуса мы получаем автодополнение кода в редакторе. Здесь нет магии, поэтому редактор может помогать нам с проверкой типов и давать подсказки.

Лучше выглядит и использование сторонних библиотек. Например, если мы хотим использовать Vuex, то можем явно импортировать функцию useStore и не засорять прототип Vue свойством this.$store. Этот подход позволяет избавиться от дополнительных манипуляций в плагинах.

const { commit, dispatch } = useStore()

Если вы хотите узнать больше о Composition API и его применениях, я рекомендую прочитать документ, в котором команда Vue объясняет причины создания нового API и предлагает кейсы, в которых он пригодится. Также есть замечательный репозиторий с примерами использования Composition API от Thorsten Lünborg, одного из членов команды ядра Vue.

Изменения в конфигурировании и монтировании

В новом Vue есть другие важные изменения в том, как мы создаём и конфигурируем наше приложение. Давайте рассмотрим это на примере.

import Vue from 'vue'
import App from './App.vue'
 
Vue.config.ignoredElements = [/^app-/]
Vue.use(/* ... */)
Vue.mixin(/* ... */)
Vue.component(/* ... */)
Vue.directive(/* ... */)
 
new Vue({
  render: h => h(App)
}).$mount('#app')

Сейчас мы используем глобальный объект Vue для конфигурирования и создания новых инстансов Vue. Любое изменение, сделанное нами в объекте Vue, будет затрагивать конечные инстансы и компоненты.

Рассмотрим, как это будет работает во Vue 3.

import { createApp } from 'vue'
import App from './App.vue'
 
const app = createApp(App)
 
app.config.ignoredElements = [/^app-/]
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)
 
app.mount('#app')

Как вы уже заметили, конфигурация относится к конкретному инстансу Vue, созданному с помощью createApp.

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

Эти изменения обсуждаются в RFC, и возможно, в будущем реализация будет другой.

Фрагменты

Ещё одна крутая фича, на которую мы можем рассчитывать во Vue 3.
Что такое фрагменты?
В настоящий момент компонент может иметь только один корневой элемент, а это значит, что код ниже работать не будет.

<template>
  <div>Hello</div>
  <div>World</div>
</template>

Причиной является то, что инстанс Vue, скрывающийся за каждым компонентом, может быть прикреплён только к одному элементу DOM. Сейчас существует способ создать компонент с несколькими корневыми элементами: для этого необходимо написать компонент в функциональном стиле, которому не нужен собственный инстанс Vue.

Оказывается, такая же проблема существует и в React-сообществе, решена она была с помощью виртуального элемента Fragment.

Выглядит это так:

class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}

Несмотря на то, что Fragment выглядит как обычный DOM-элемент, он виртуальный и не будет создаваться в DOM-дереве. С этим подходом мы можем использовать функциональность одного корневого элемента без создания лишнего элемента в DOM.

Сейчас вы можете использовать фрагменты и во Vue 2, но с помощью библиотеки vue-fragments, а во Vue 3 они будут работать из коробки!

Suspense

Еще одна отличная идея из экосистемы React, которая будет реализована во Vue 3, — это Suspense.

Suspense приостанавливает рендеринг компонента и отображает заглушку до выполнения определённых условий. На конференции Vue London Эван Ю вскользь затронул Suspense и показал API, который мы можем ожидать в будущем. Suspense-компонент будет иметь 2 слота: для контента и для заглушки.

<Suspense>
  <template >
    <Suspended-component />
  </template>
  <template #fallback>
    Loading...
  </template>
</Suspense>

Заглушка будет отображаться до тех пор, пока компонент <Suspended-component/> не будет готов. Компонент Suspense также может ожидать загрузку асинхронного компонента или выполнения каких-то асинхронных действий в setup-функции.

Несколько v-models

v-model — это директива, с помощью которой можно использовать двусторонний биндинг. Мы можем передать реактивное свойство и изменить его внутри компонента.

Нам она хорошо известна по работе с элементами форм.

<input v-bind="property />

Но знали ли вы, что v-model можно использовать с любым компонентом? Под капотом v-model является лишь пробросом параметра value и прослушиванием события input.

Переписать предыдущий пример с использованием этого синтаксиса можно следующим образом:

<input 
  v-bind:value="property"
  v-on:input="property = $event.target.value"
/>

Можно даже изменить названия свойства и события по умолчанию с помощью опции model:

model: {
  prop: 'checked',
  event: 'change'
}

Как видно, директива v-model может быть очень полезным «синтаксическим сахаром», если мы хотим использовать двусторонний биндинг в наших компонентах. К сожалению, на компонент может быть лишь одна v-model.

К счастью, во Vue 3 эта проблема будет решена. Мы сможем передать имя в v-model и использовать столько v-model, сколько необходимо.

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

<InviteeForm
  v-model:name="inviteeName"
  v-model:email="inviteeEmail"
/>

Эти изменения обсуждаются в RFC, и возможно, в будущем реализация будет другой.

Portals

Порталы — это компоненты, созданные для рендера контента вне иерархии текущего компонента. Это тоже одна из возможностей, реализованных в React. В документации React порталы описываются следующим образом: «Порталы позволяют рендерить дочерние элементы в DOM-узел, который находится вне DOM-иерархии родительского компонента».

Порталы отлично подходят для реализации таких компонентов, как модальные окна, попапы и всех тех, которые необходимо отобразить поверх страницы.

При использовании порталов вы можете быть уверены, что стили родительского компонента не повлияют на дочерний. Это также избавит вас от грязных хаков с z-index.

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

Ниже представлен вариант реализации на библиотеке portal-vue, которая добавляет порталы во Vue 2.

<portal to="destination">
  <p>This slot content will be rendered wherever thportal-target with name 'destination'
    is  located.</p>
</portal>
 
<portal-target name="destination">
  <!--
  This component can be located anywhere in your App.
  The slot content of the above portal component wilbe rendered here.
  -->
</portal-target>

А во Vue 3 данная фича будет из коробки.

Новое API пользовательских директив

API пользовательских директив немного изменится во Vue 3, чтобы больше соответствовать жизненному циклу компонента. Создание директив станет более интуитивным, а значит, и более простым для понимания и изучения новичками.

Сейчас объявление пользовательской директивы выглядит так:

const MyDirective = {
  bind(el, binding, vnode, prevVnode) {},
  inserted() {},
  update() {},
  componentUpdated() {},
  unbind() {}
}

А во Vue 3 будет выглядеть так:

const MyDirective = {
  beforeMount(el, binding, vnode, prevVnode) {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {}, // new
  unmounted() {}
}

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

Этот API так же обсуждается и может измениться в будущем.

Резюме

Рядом со значительным нововведением — Composition API — мы можем найти несколько улучшений поменьше. Очевидно, что Vue движется в сторону улучшения опыта разработчика, к упрощению и интуитивизации API. Так же круто видеть, что команда Vue решила добавить в ядро фреймворка много идей, которые уже реализованы в сторонних библиотеках.

Список выше содержит только наиболее важные улучшения и изменения API. Если вам захотелось узнать и о других, загляните репозиторий RFC.

Автор: Дмитрий Татаринцев

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js