Vue.js — соображения и трюки

Alexey Pyltsyn
devSchacht
Published in
7 min readJun 21, 2018

Перевод статьи Harshal Patil: Vue.js — Considerations and Tricks.

Vue.js — отличный инструмент. Однако, когда вы начинаете создавать крупномасштабные приложения на JavaScript, вы сталкиваетесь с границами Vue.js. Эти границы на самом деле не ограничивают фреймворк, скорее это важные решения проектирования, принимаемые периодически командой Vue.js.

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

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

Опять же, приведённые обсуждения актуальны на день написания статьи (18 мая 2018 года). При обновлении фреймворка, браузера или JS API, они могут потерять достоверность и стать непонятными.

1. Почему Vue.js не использует ES-классы из коробки для компонентов?

Если вы пришли из фреймворка, похожего на Angular, или из какого-нибудь языка высокого уровня для бэкенда приложения, ваш первый вопрос мог быть таким: почему не использовать классы для создания компонентов?

Создатель Vue.js, Эван Ю, отлично ответил на этот вопрос в комментарии на GitHub:

Вот три главные причины, по которым не используются классы для объявления компонентов:

  1. ES-классы недостаточно хороши для удовлетворения требований текущего API Vue.js. Классы в ES не полностью реализованы и их часто критикуют как шаг в неверном направлении. Классы с приватными полями и декораторами, после их стабилизации (достижения stage 3, как минимум), возможно, помогут.
  2. ES-классы отлично подходят только тем, кто знаком с языками программирования с поддержкой ООП. Это просто исключает значительную часть веб-сообщества, которая не использует сложные инструменты для сборки проектов или транспилеры.
  3. Создание отличной иерархии компонентов пользовательского интерфейса (UI) — это отличная композиция компонентов. Речь идёт не о большой иерархии наследования. К сожалению, классы в ES лучше подходят для последнего.

2. Как я могу создать собственный абстрактный компонент?

Если создания крупномасштабных приложений недостаточно, у вас может появится сумасшедшая идея реализации абстрактного компонента, например <transition> или <router-view>. Определённо была дискуссия по этому поводу, но в реальности она ничем не кончилась.

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

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

3. Мне не очень нравится подход с однофайловым компонентом. Я больше привык к разделению HTML, CSS и JavaScript

Никто не мешает вам сделать подобное. Если вы философ старой школы «важности разделения», который любит буквально всё разделять на отдельные файлы или ненавидеть странное поведение с файлами .vue, то, безусловно, это возможно. Всё, что вам нужно сделать:

<!-- https://ru.vuejs.org/v2/guide/single-file-components.html -->

<!-- my-component.vue -->
<template src="./my-component.html"></template>
<script src="./my-component.js"></script>
<style src="./my-component.css"></style>

Однако сразу возникает следующий вопрос: мне всегда нужно 4 файла (vue + html + js + css) для моего компонента. Могу ли я избавиться каким-то образом от файлов .vue? Ответ - определённо, да, у вас есть такая возможность, используя vue-template-loader.

Мои коллеги написали по поводу этого отличный пост:

4. Функциональные компоненты

Благодаря React.js функциональные компоненты стали сумасшествием, пусть и с благими намерениями: они быстрые, без состояния и легко тестируются. Тем не менее, у них есть подводные камни.

4.1 Почему я не могу использовать класс, основанный на декораторе @Component для функциональных компонентов?

Снова возвращаясь к классам, следует отметить, что классы представляют собой структуры данных для хранения локального состояния. Если функциональные компоненты не имеют состояния, то нет никакого смысла в использовании декоратора @Component.

Соответствующее обсуждение имеется по адресу:

4.2 Внешние классы не применяются к функциональным компонентам

Функциональные компоненты не имеют привязки к классу и стилю, как обычные компоненты. Нужно вручную применять эти привязки внутри функции отрисовки.

4.3 Функциональные компоненты всегда повторно отрисовываются?

TLDR: будьте осторожны при использовании компонентов с состоянием внутри функциональных компонентов

Функциональные компоненты — нетерпеливы, что означает, что функция отрисовки вызывается напрямую. Это также означает, что вы должны:

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

Функциональный компонент лучше использовать, если они являются листовыми компонентами (leaf components).

4.4 Как сгенерировать событие из функционального компонента?

Генерация события из функционального компонента не очевидна. К сожалению, об этом не написано в документации. Метод $emit недоступен в функциональном компоненте. Следующий вопрос на Stack Overflow может помочь.

5. Прозрачные компоненты-обёртки

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

<!-- Обёртка компонента для поля ввода -->
<template>
<div class="wrapper-comp">
<label>Моя метка</label>
<input @focus="$emit('focus')" type="text"/>
</div>
</template>

В коде выше нас в действительности интересует тег input, а не корневой элемент div, поскольку он добавлен в основном для стилизации и косметической цели. Пользователя компонента могут интересовать несколько событий поля ввода, такие как blur, focus, click, hover и т.д. Это означает, что мы должны повторно генерировать каждое событие. Наш компонент будет выглядеть так.

<!-- Обёртка компонента для поля ввода -->
<template>
<div class="wrapper-comp">
<label>Моя метка</label>
<input type="text"
@focus="$emit('focus')"
@click="$emit('click')"
@blur="$emit('blur')"
@hover="$emit('hover')"
/>
</div>
</template>

Этот код противоречит принципу DRY (не повторяй себя) и выглядит грязным. Простое решение состоит в том, чтобы просто повторно связать ваши слушатели событий с требуемыми DOM-элементам, используя свойство vm.$listeners.

<!-- Обратите внимание на использование $listeners -->
<template>
<div class="wrapper-comp">
<label>Моя метка</label>
<input v-on="$listeners" type="text"/>
</div>
</template>
<!-- Использование: событие @focus связывается ко внутреннему элементу ввода -->
<custom-input @focus="onFocus"></custom-input>

6. Почему невозможно использовать v-on или генерировать события из слота

Я часто видел, как разработчики пытались генерировать событие из слота или прослушивать событие в слоте. Это просто невозможно.

Компонент slot предоставляется посредством вызова/родительским компонентом. Это означает, что все события должны связываться с вызывающим компонентом. Попытка прослушать эти изменения означает, что ваш родительский и дочерний компоненты тесно связаны, и есть ещё один способ сделать это, который красиво объяснил Эван Ю:

7. Слот в слоте (читай: внук слота)

В какой-то момент вы столкнетесь с таким сценарием. Представьте, что у вас есть компонент, например, A, который принимает несколько слотов. Следуя принципам композиции, вы создаёте другой компонент B, использующий компонент A. Теперь вы компонент B и используете его в компоненте C.

Вопрос: как передать слот из компонента C в компонент A?

Ответ на этот вопрос зависит от того, что вы используете? Если вы используете функцию отрисовки, то это довольно тривиально. Функция отрисовки B будет такой:

// Функция render для компонента B
function render(h) {
return h('component-a', {
// Передача слотов, так как они относятся к компоненту A
scopedSlot: this.$scopedSlots
}
}

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

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

Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.

Статья на GitHub

--

--