Я помню, как однажды взял в руки CakePHP. Мне понравилось, как легко было начать с этим. Документы были не только хорошо структурированными и исчерпывающими, но и удобными для пользователей. Спустя годы это именно то, что я обнаружил с помощью Vue.js. Тем не менее, есть одна вещь, которой все еще не хватает документации Vue по сравнению с Cake: учебное пособие по реальному проекту.

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

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

TL; DR: в этом посте подробно рассказывается, как и почему. Он разработан, чтобы помочь вам понять некоторые основные концепции Vue.js и научить принимать дизайнерские решения для ваших будущих проектов. Если вы хотите понять весь мыслительный процесс, читайте дальше. В противном случае вы можете посмотреть окончательный код на CodeSandbox.

Начиная

Vue.js (по праву) гордится тем, что его можно запускать как простой скрипт, но все немного по-другому, когда вы хотите использовать однофайловые компоненты. Теперь у вас нет необходимости создавать компоненты таким образом. Вы можете легко обойтись определением глобального компонента с помощью Vue.component.

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

Чтобы упростить задачу и сократить время настройки, мы будем использовать vue-cli и шаблон webpack-simple Vue.js.

Во-первых, вам нужно установить vue-cli глобально. Запустите свой терминал и введите следующее:

npm install -g vue-cli

Теперь вы можете сгенерировать готовые к использованию шаблоны Vue.js несколькими нажатиями клавиш. Вперед и введите:

vue init webpack-simple path/to/my-project

Вам зададут несколько вопросов. Выберите значения по умолчанию для всех, кроме «Использовать sass», на который вы должны ответить «да» (y). Затем vue-cli инициализирует проект и создаст файл package.json. Когда это будет сделано, вы можете перейти в каталог проекта, установить зависимости и запустить проект:

cd path/to/my-project
npm install
npm run dev

Вот и все! Webpack начнет обслуживать ваш проект через порт 8080 (если он доступен) и запустит его в вашем браузере. Если все прошло хорошо, вы должны увидеть такую ​​страницу приветствия.

Мы уже на месте?

Почти! Чтобы правильно отладить компонент Vue.js, вам понадобятся правильные инструменты. Идите вперед и установите расширение браузера Vue.js devtools (Firefox / Chrome / Safari).

Ваш первый компонент

Одной из лучших функций Vue.js являются однофайловые компоненты (SFC). Они позволяют определять структуру, стиль и поведение компонента в одном файле без обычных недостатков смешивания HTML, CSS и JavaScript.

SFC оканчиваются расширением .vue и имеют следующую структуру:

<template>
  <!-- Your HTML goes here -->
</template>
<script>
  /* Your JS goes here */
</script>
<style>
  /* Your CSS goes here */
</style>

Пойдем и создадим наш первый компонент: создадим Rating.vue файл в /src/components и скопируем / вставим приведенный выше фрагмент кода. Затем откройте /src/main.js и адаптируйте существующий код:

import Vue from 'vue'
import Rating from './components/Rating'
new Vue({
  el: '#app',
  template: '<Rating/>',
  components: { Rating }
})

Наконец, добавьте немного HTML в свой Rating.vue:

<template>
  <ul>
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
  </ul>
</template>

Теперь посмотрите на страницу в своем браузере, и вы должны увидеть список! Vue.js прикрепил ваш компонент <Rating> к элементу #app в index.html. Если вы проверите HTML, вы не увидите никаких признаков элемента #app: Vue.js заменил его на компонент.

Примечание: вы заметили, что вам даже не нужно перезагружать страницу? Это потому, что vue-loader Webpack имеет функцию горячей перезагрузки. В отличие от оперативной перезагрузки или синхронизации браузера, горячая перезагрузка не обновляет страницу при каждом изменении файла. Вместо этого он отслеживает изменения компонентов и только обновляет их, сохраняя состояние неизменным.

Итак, мы потратили некоторое время на настройку, но пришло время написать осмысленный код.

Шаблон

Мы собираемся использовать vue-awesome, компонент значков SVG для Vue.js, созданный с помощью Font Awesome icons. Это позволяет нам загружать только нужные нам значки. Идите вперед и установите его с помощью npm (или Yarn):

npm install vue-awesome

Затем отредактируйте свой компонент следующим образом:

<template>
  <div>
    <ul>
      <li><icon name="star"/></li>
      <li><icon name="star"/></li>
      <li><icon name="star"/></li>
      <li><icon name="star-o"/></li>
      <li><icon name="star-o"/></li>
    </ul>
    <span>3 of 5</span>
  </div>
</template>
<script>
import 'vue-awesome/icons/star'
import 'vue-awesome/icons/star-o'
import Icon from 'vue-awesome/components/Icon'
export default {
  components: { Icon }
}
</script>

Хорошо, хорошо, давай притормози и все это объясним 😅

Vue.js использует собственные модули ES6 для обработки зависимостей и экспорта компонентов. Первые две строки в <script> значках импорта блокируют по отдельности, поэтому вы не получите ненужных значков в окончательном наборе. Третий импортирует компонент Icon из vue-awesome, чтобы вы могли использовать его в своем.

Icon - это SFC на Vue.js, подобный той, которую мы создаем. Если вы откроете файл, вы увидите, что он имеет ту же структуру, что и наш.

Блок export default экспортирует литерал объекта как модель представления нашего компонента. Мы зарегистрировали компонент Icon в свойстве components, чтобы мы могли использовать его локально в нашем.

Наконец, мы использовали Icon в нашем HTML <template> и передали ему свойство name, чтобы определить, какой значок мы хотим. Компоненты можно использовать как пользовательские HTML-теги, преобразовав их в kebab-case (например: MyComponent становится <my-component>). Нам не нужно ничего вкладывать внутрь компонента, поэтому мы использовали самозакрывающийся тег.

Примечание: вы заметили, что мы добавили <div> обтекание HTML? Это потому, что мы также добавили счетчик в <span> на корневом уровне, а шаблоны компонентов в Vue.js принимают только один корневой элемент. Если вы этого не сделаете, вы получите ошибку компиляции.

Стиль

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

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

React имеет стилизованные компоненты, Vue.js имеет компонентный CSS. Он позволяет вам писать CSS для конкретных компонентов, не придумывая уловок, чтобы сохранить его. Вы пишете обычный CSS с «нормальными» именами классов, а Vue.js обрабатывает область видимости, назначая атрибуты данных элементам HTML и добавляя их к скомпилированным стилям.

Давайте добавим в компонент несколько простых классов:

<template>
  <div class="rating">
    <ul class="list">
      <li class="star active"><icon name="star"/></li>
      <li class="star active"><icon name="star"/></li>
      <li class="star active"><icon name="star"/></li>
      <li class="star"><icon name="star-o"/></li>
      <li class="star"><icon name="star-o"/></li>
    </ul>
    <span>3 of 5</span>
  </div>
</template>

И стилизуем его:

<style scoped>
  .rating {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    font-size: 14px;
    color: #a7a8a8;
  }
  .list {
    margin: 0 0 5px 0;
    padding: 0;
    list-style-type: none;
  }
  .list:hover .star {
    color: #f3d23e;
  }
  .star {
    display: inline-block;
    cursor: pointer;
  }
  .star:hover ~ .star:not(.active) {
    color: inherit;
  }
  .active {
    color: #f3d23e;
  }
</style>

Видите этот атрибут scoped наверху? Это то, что сообщает Vue.js о том, что стили нужно ограничить, чтобы они больше никуда не просочились. Если вы скопируете / вставите HTML-код прямо в index.html, вы заметите, что ваши стили не будут применяться: это потому, что они привязаны к компоненту! 🎉

А препроцессоры?

Vue.js позволяет легко переключиться с простого CSS на ваш любимый препроцессор. Все, что вам нужно, это правильный загрузчик Webpack и простой атрибут в блоке <style>. Мы сказали да Использовать sass при создании проекта, поэтому vue-cli уже установил и настроил sass-loader для нас. Теперь все, что нам нужно сделать, это добавить lang="scss" к открывающему тегу <style>.

Теперь мы можем использовать Sass для написания стилей на уровне компонентов, импорта частичных данных, таких как переменные, определения цветов или миксинов и т. Д. Если вы предпочитаете синтаксис с отступом (или нотацию «sass»), просто переключите scss на sass в атрибуте lang.

Поведение

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

<script>
  ...
  export default {
    components: { Icon },
    data() {
      return {
        stars: 3,
        maxStars: 5
      }
    }
  }
</script>
/* ... */
<template>
  <div class="rating">
    <ul class="list">
      <li v-for="star in maxStars" :class="{ 'active': star <= stars }" class="star">
        <icon :name="star <= stars ? 'star' : 'star-o'"/>
      </li>
    </ul>
    <span>3 of 5</span>
  </div>
</template>

Здесь мы использовали data Vue для настройки состояния компонента. Каждое свойство, которое вы определяете в data, становится реактивным: если оно изменяется, оно будет отражено в представлении.

Мы создаем повторно используемый компонент, поэтому data должна быть фабричной функцией, а не литералом объекта. Таким образом, мы получаем новый объект вместо ссылки на существующий, который будет использоваться несколькими компонентами.

Наша data фабрика возвращает два свойства: stars, текущее количество «активных» звезд и maxStars, общее количество звезд для компонента. Исходя из этого, мы адаптировали наш шаблон, чтобы он отражал фактическое состояние компонента. Vue.js поставляется с набором директив, которые позволяют добавлять логику представления в шаблон, не смешивая ее с простым JavaScript. Директива v-for перебирает любой повторяемый объект (массивы, литералы объектов, карты и т. Д.). В качестве диапазона также может использоваться число, которое нужно повторять x раз. Это то, что мы сделали с v-for="star in maxStars", поэтому у нас есть <li> для каждой звезды в компоненте.

Вы могли заметить, что некоторые свойства имеют префикс двоеточия: это сокращение для директивы v-bind, которая динамически связывает атрибуты с выражением. Мы могли бы написать это в полной форме, v-bind:class.

Нам нужно добавить класс active к элементам <li>, когда звезда активна. В нашем случае это означает, что каждый <li>, индекс которого меньше stars, должен иметь active класс. Мы использовали выражение в директиве :class, чтобы добавить active только тогда, когда текущий star меньше stars. Мы использовали то же условие, на этот раз с тернарным оператором, чтобы определить, какой значок использовать с компонентом Icon: star или star-o.

А что насчет счетчика?

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

<span>{{ stars }} of {{ maxStars }}</span>

Довольно прямолинейно, не правда ли? В нашем случае это помогает. Но если нам нужно более сложное выражение JavaScript, было бы лучше абстрагировать его в вычисляемом свойстве.

export default {
  ...
  computed: {
    counter() {
      return `${this.stars} of ${this.maxStars}`
    }
  }
}
/* ... */
<span>{{ counter }}</span>

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

Еще нам нужно обеспечить способ спрятать счетчик, если он нам не нужен. Самый простой способ сделать это - использовать директиву v-if с логическим значением.

<span v-if="hasCounter">{{ stars }} of {{ maxStars }}</span>
/* ... */
export default {
  ...
  data() {
    return {
      stars: 3,
      maxStars: 5,
      hasCounter: true
    }
  }
}

Интерактивность

Мы почти закончили, но нам еще предстоит реализовать самую интересную часть нашего компонента: реактивность. Мы собираемся использовать v-on, директиву Vue.js, которая обрабатывает события, и methods, свойство Vue.js, к которому вы можете прикрепить все свои методы.

<template>
  ...
  <li @click="rate(star)" ...>
  ...
</template>
/* ... */
export default {
  ...
  methods: {
    rate(star) {
      // do stuff
    }
  }
}

Мы добавили атрибут @click в <li>, который является сокращением для v-on:click. Эта директива содержит вызов метода rate, который мы определили в свойстве methods компонента.

«Подождите ... это выглядит ужасно знакомым с атрибутом onclick HTML. Разве использование встроенного JavaScript в HTML - не устаревшая и плохая практика? »

Это действительно так, но даже несмотря на то, что синтаксис очень похож на onclick, сравнение этих двух было бы ошибкой. Когда вы создаете компонент Vue.js, вы не должны думать о нем как об отдельном HTML / CSS / JS, а скорее как об одном компоненте, который использует несколько языков. Когда проект обслуживается в браузере или компилируется для производства, весь HTML и директивы компилируются в простой JavaScript. Если вы проверите обработанный HTML-код, вы не увидите никаких признаков ваших директив или каких-либо onclick атрибутов. Vue.js скомпилировал ваш компонент и создал правильные привязки.

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

Вернемся к нашему rate методу. Нам нужно преобразовать stars в индекс элемента, по которому щелкнули мышью, поэтому мы передаем индекс из директивы @click и можем сделать следующее:

export default {
  ...
  methods: {
    rate(star) {
      this.stars = star
    }
  }
}

Зайдите на страницу в браузере и попробуйте нажать на звездочки: работает!

Если вы откроете панель Vue в инструментах разработчика своего браузера и выберете компонент <Rating>, вы увидите, как данные меняются при нажатии на звездочки. Это показывает, что ваше свойство stars является реактивным: когда вы его изменяете, оно отправляет свои изменения в представление. Эта концепция называется привязкой данных, с которой вам следует ознакомиться, если вы когда-либо использовали такие фреймворки, как Backbone.js или Knockout. Разница в том, что Vue.js, как и React, делает это только в одном направлении: это называется односторонняя привязка данных. Но эта тема заслуживает отдельной статьи 😊

На данный момент мы могли бы назвать это готовым, но мы могли бы немного поработать, чтобы улучшить взаимодействие с пользователем.

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

export default {
  ...
  methods: {
    rate(star) {
      this.stars = this.stars === star ? star - 1 : star
    }
  }
}

Теперь, если индекс выбранной звезды равен текущему значению stars, мы уменьшаем его значение. В противном случае мы присваиваем ему значение star.

Если мы хотим быть внимательными, мы должны также добавить уровень элементов управления, чтобы гарантировать, что stars никогда не будет присвоено значение, которое не имеет смысла. Нам нужно убедиться, что stars никогда не бывает меньше 0, никогда не больше maxStars и что это правильное число.

export default {
  ...
  methods: {
    rate(star) {
      if (typeof star === 'number' && star <= this.maxStars && star >= 0) {
        this.stars = this.stars === star ? star - 1 : star
      }
    }
  }
}

Проходящий реквизит

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

export default {
  props: ['grade', 'maxStars', 'hasCounter'],
  data() {
    return {
      stars: this.grade
    }
  },
  ...
}

И в main.js:

new Vue({
  el: '#app',
  template: '<Rating :grade="3" :maxStars="5" :hasCounter="true"/>',
  components: { Rating }
})

Здесь нужно отметить три вещи:

Во-первых, мы использовали сокращение v-bind для передачи свойств из экземпляра компонента: это то, что Vue.js называет динамическим синтаксисом. Он не нужен, если вы хотите передать строковое значение, для которого будет работать буквальный синтаксис (обычный атрибут без v-bind). Но в нашем случае, поскольку мы передаем числа и логические значения, это важно.

Свойства props и data объединяются во время компиляции, поэтому нам не нужно изменять способ вызова свойств ни в модели представления, ни в шаблоне. Но по той же причине мы не можем использовать одинаковые имена для свойств props и data.

Наконец, мы определили свойство grade и передали его как значение stars в свойстве data. Причина, по которой мы сделали это вместо прямого использования grade prop, заключается в том, что значение будет изменено, когда мы изменим оценку. В Vue.js реквизиты передаются от родителей к детям, а не наоборот, поэтому вы случайно не измените состояние родителя. Это противоречило бы принципу одностороннего потока данных и затруднило бы отладку. Вот почему вам не следует пытаться изменить свойство внутри дочернего компонента. Вместо этого определите локальное свойство data, которое использует начальное значение свойства как собственное.

Последние штрихи

Прежде чем мы завершим этот день, мы должны посетить еще одну доброту Vue.js: проверка допуска.

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

export default {
  props: {
    grade: {
      type: Number,
      required: true
    },
    maxStars: {
      type: Number,
      default: 5
    },
    hasCounter: {
      type: Boolean,
      default: true
    }
  },
  ...
}

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

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

<Rating :grade="3"/>

Вот и все! Вы только что создали свой первый компонент Vue.js и изучили множество концепций, включая создание шаблонного проекта с vue-cli, однофайловые компоненты, импорт компонентов в компонентах, стили с заданной областью действия, директивы, обработчики событий. », Вычисляемые свойства , пользовательские методы , односторонний поток данных , props и prop validation . И это только малая часть того, что может предложить Vue.js!

Это довольно подробное руководство, поэтому не волнуйтесь, если вы не все поняли. Прочтите его еще раз, делайте паузы между разделами, исследуйте и возитесь с кодом на CodeSandbox. И если у вас есть какие-либо вопросы или замечания по поводу учебника, не стесняйтесь писать мне в Twitter!

Первоначально опубликовано на frontstuff.io.