Статьи

Расширение компонентов Vue.js

Есть ли в вашем приложении Vue компоненты, которые имеют одинаковые параметры или даже разметку шаблона?

Было бы неплохо создать базовый компонент с общими параметрами и разметкой, а затем расширить базовый компонент для создания подкомпонентов . Такая архитектура поможет вам применить принцип DRY в вашем коде (не повторяйте себя), который может сделать ваш код более читабельным и уменьшить вероятность ошибок.

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

Пример: вопросы опроса

Вот простой опрос, сделанный с помощью Vue.js:

Название изображения

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

  1. Ввод текста
  2. Выберите вход
  3. Радио вход

Хорошей архитектурой было бы превращение каждого типа вопроса / ввода в отдельный, повторно используемый компонент. Я назвал их в соответствии с вышеизложенным:

  1. SurveyInputText
  2. SurveyInputSelect
  3. SurveyInputRadio

Имеет смысл, что каждый вопрос / ввод является отдельным компонентом, потому что каждому нужна своя разметка (например, <input type="text">vs <input type="radio">), и каждому также нужны свои реквизиты, методы и т. Д. Тем не менее, эти компоненты будут иметь много общего:

  • Вопрос.
  • Функция проверки.
  • Состояние ошибки.
  • И т.п.

Поэтому я считаю, что это отличный вариант использования для расширения компонентов!

Базовый компонент

Каждый из подкомпонентов будет наследоваться от одного названного файлового компонента SurveyInputBase. Обратите внимание на следующее:

  • questionОпора будет общим для каждого компонента. Мы могли бы добавить более распространенные опции, но давайте остановимся только на одном для этого простого примера.
  • Нам как-то нужно скопировать реквизиты из этого компонента в любой расширяющий компонент.
  • Нам как-то нужно вставить разметку для разных входов внутри шаблона.

SurveyInputBase.vue

<template>
  <div class="survey-base">
    <h4>{{ question }}</h4>
    <!--the appropriate input should go here-->
  </div>
</template>
<script>
  export default {
    props: [ 'question' ],
  }
</script>

Наследование опций компонента

На мгновение забыв о шаблоне, как мы можем получить каждый подчиненный компонент для наследования реквизита? Каждому понадобится questionкак опора, так и свои уникальные реквизиты:

Название изображения

Это может быть достигнуто путем импорта базового компонента и указания на него с помощью extendsопции:

SurveyInputText.vue

<template>
  <!--How to include the question here???-->
  <input :placeholder="placeholder">
</template>
<script>
  import SurveyInputBase from './SurveyInputBase.vue';
  export default {
    extends: SurveyInputBase,
    props: [ 'placeholder' ],
  }
</script>

Глядя в Vue Devtools, мы видим, что использование extendsдействительно дало нам базовый реквизит нашего подкомпонента:

Название изображения

Стратегия слияния

Вам может быть интересно, как субкомпонент унаследовал questionреквизит вместо того, чтобы перезаписывать его. extendsВариант реализует стратегию объединения , которая обеспечит ваши варианты комбинируются правильно. Например, если реквизиты имеют разные имена, они, очевидно, будут включены, но если они имеют одинаковое имя, Vue предпочтет субкомпонент.

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

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

Расширение шаблона

Расширить опции компонента довольно просто — а как же шаблон?

Стратегия слияния не работает с templateопцией. Мы либо наследуем базовый шаблон, либо определяем новый и перезаписываем его. Но как мы можем их объединить ?

Мое решение этого заключается в использовании препроцессора Pug . Он поставляется с includeи extendsвариантами , так что кажется, хорошо подходит для этого шаблона.

Базовый компонент

Во-первых, давайте конвертируем шаблон нашего базового компонента в синтаксис Pug:

<template lang="pug">
  div.survey-base
    h4 {{ question }}
    block input
</template>

Обратите внимание на следующее:

  • Мы добавляем lang="pug"в наш шаблон тег, чтобы Vue-Loader обрабатывал его как шаблон Pug (также не забудьте также добавить модуль Pug в ваш проект npm i --save-dev pug)
  • Мы используем, block inputчтобы объявить выход для субкомпонентного контента.

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

SurveyInputBase.pug

div.survey-base
  h4 {{ question }}
  block input

Затем мы помещаем includeэтот файл в наш базовый компонент, так что он все еще может использоваться как обычный автономный компонент:

SurveyInputBase.vue

<template lang="pug">
  include SurveyInputBase.pug
</template>
<script>
  export default {
    props: [ 'question' ]
  }
</script>

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

Подкомпонент

Теперь давайте также преобразуем шаблон нашего подкомпонента в Pug:

SurveyInputText.vue

<template lang="pug">
  extends SurveyInputBase.pug
  block input
    input(type="text" :placeholder="placeholder")
</template>
<script>
  import SurveyInputBase from './SurveyInputBase.vue';
  export default {
    extends: SurveyInputBase,
    props: [ 'placeholder' ],
  }
</script>

Подкомпоненты используют extendsфункцию Pug, которая включает в себя базовый компонент и выводит любой пользовательский контент в inputблоке (концепция, аналогичная слотам ).

Вот как будет эффективно выглядеть шаблон подкомпонента после расширения базы и перевода обратно в обычный шаблон HTML Vue:

<div class="survey-base">
  <h4>{% raw %}{{ question }}{% endraw %}</h4>
  <input type="text" :placeholder="placeholder">
</div>

Собери все вместе

Используя эту стратегию , мы можем пойти дальше и создать две другие компоненты к югу SurveyInputSelectи SurveyInputRadioсо своим собственным реквизитом и разметкой.

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

<survey-input-text
  question="1. What is your name?"
  placeholder="e.g. John Smith"
></survey-input-text>

<survey-input-select
  question="2. What is your favorite UI framework?"
  :options="['React', 'Vue.js', 'Angular']"
></survey-input-select>

<survey-input-radio
  question="3. What backend do you use?"
  :options="['Node.js', 'Laravel', 'Ruby']"
  name="backend"
>
</survey-input-radio>

А вот визуализированная разметка:

<div class="survey-base">
  <h4>1. What is your name?</h4>
  <input type="text" placeholder="e.g. John Smith">
</div>
<div class="survey-base">
  <h4>2. What is your favorite UI framework?</h4>
  <select>
    <option>React</option>
    <option>Vue.js</option>
    <option>Angular</option>
  </select>
</div>
<div class="survey-base">
  <h4>3. What backend do you use?</h4>
  <div><input type="radio" name="backend" value="Node.js">Node.js</div>
  <div><input type="radio" name="backend" value="Laravel">Laravel</div>
  <div><input type="radio" name="backend" value="Ruby">Ruby</div>
</div>

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