Урок 9 :: Components & Props :: Компоненты и реквизиты (пропсы)

Вот, что мы с Вами сделаем на этом уроке.
Vue Mastery
Пройдем по этой ссылке:
Components & Props ➳

... Все внимательно послушаем, посмотрим, прочитаем.

Затем сделаем конспект (мой см ниже).

Конспект

Компоненты и реквизиты (Components & Props)
В этом уроке мы рассмотрим концепцию компонентов и реквизитов.

Чтобы начать этот урок, вы можете либо получить программой git коммандой checkout исходный код в ветке
L9-start репозитория, либо перейти в CodePen, чтобы взять файлы там.

Примечание переводчика

Чтобы перейти на ветку с файлами начала урока 9 выполните команду:

git checkout L9-start

Наша Цель
Выполнить рефакторинг нашего тестового приложения, чтобы использовать компонент продукта (product). И этот компонент пусть использует реквизит (prop).

Строительные блоки приложения Vue
В современных интерфейсных JavaScript-фреймворках компоненты являются строительными блоками приложения, и это, безусловно, относится и к Vue. Вы можете представить себе компоненты, немного похожие на блоки Лего (Legos), которые вы можете подключать друг к другу в иерархии генеалогического дерева компонентов.
les09_pic01.jpg
Любая типичная веб-страница может состоять из нескольких компонентов. Обычное дело для комонентов быть "родителями", когда у них есть дочерние компоненты, вложенные в них.
les09_pic02.jpg
Создание нашего первого компонента
Давайте зайдем в наше приложение и создадим наш первый компонент. Поскольку наше приложение в конечном итоге будет иметь несколько компонентов, мы создадим папку компонентов components, внутри которой создадим наш первый компонент, называемый ProductDisplay.js.
les09_pic03.jpg
Синтаксис для создания компонента выглядит следующим образом:

📄 components/ProductDisplay.js
app.component('product-display', {})

Первый аргумент - это имя компонента, в данном случае 'product-display', а второй аргумент - объект для настройки нашего компонента (аналогично объекту options, используемому для настройки нашего корневого приложения Vue).

Шаблон
Поскольку нам нужно, чтобы наш компонент имел структуру, мы добавим свойство шаблона (template) и вставим весь HTML-код на основе продукта, который в настоящее время находится в index.html сюда, в литерал шаблона (template literal).

📄 components/ProductDisplay.js
app.component('product-display', {
  template: 
    /*html*/ 
    `<div class="product-display">
      <div class="product-container">
        <div class="product-image">
          <img v-bind:src="image">
        </div>
        <div class="product-info">
          <h1>{{ title }}</h1>
  
          <p v-if="inStock">In Stock</p>
          <p v-else>Out of Stock</p>
  
          <div 
            v-for="(variant, index) in variants" 
            :key="variant.id" 
            @mouseover="updateVariant(index)" 
            class="color-circle" 
            :style="{ backgroundColor: variant.color }">
          </div>
          
          <button 
            class="button" 
            :class="{ disabledButton: !inStock }" 
            :disabled="!inStock" 
            v-on:click="addToCart">
            Add to Cart
          </button>
        </div>
      </div>
    </div>`
})

Обратите внимание, что мы не изменили ничего из этого кода, мы просто перемещаем его в компонент отображения продукта product-display, чтобы он был там инкапсулирован. Если вам интересно, что там делает /*html*/, ответ простой. Эта строка активирует расширение VS-кода es6-string-html , которое дает нам подсветку синтаксиса, даже если мы находимся в этом литерале шаблона.

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

📄 components/ProductDisplay.js
app.component('product-display', {
  template: 
    /*html*/ 
    `<div class="product-display">
      ...
    </div>`,
  data() {
    return {
        product: 'Socks',
        brand: 'Vue Mastery',
        selectedVariant: 0,
        details: ['50% cotton', '30% wool', '20% polyester'],
        variants: [
          { id: 2234, color: 'green', image: './assets/images/socks_green.jpg', quantity: 50 },
          { id: 2235, color: 'blue', image: './assets/images/socks_blue.jpg', quantity: 0 },
        ]
    }
  },
  methods: {
      addToCart() {
          this.cart += 1
      },
      updateVariant(index) {
          this.selectedVariant = index
      }
  },
  computed: {
      title() {
          return this.brand + ' ' + this.product
      },
      image() {
          return this.variants[this.selectedVariant].image
      },
      inStock() {
          return this.variants[this.selectedVariant].quantity
      }
  }
})

Мы обязательно удалим корзину (cart) из данных (data) здесь, потому что нам не нужно, чтобы у каждого товара была своя корзина.

Очистка main.js
Теперь, когда мы инкапсулировали весь этот код для конкретного продукта в наш компонент отображения продукта product-display, мы можем очистить наш main.js файл.

📄 main.js
const app = Vue.createApp({
  data() {
    return {
      cart: 0,
    }
  },
  methods: {}
})

Мы оставили опции корзина (cart) и методы (methods), потому что позже мы здесь сделаем новый метод.

Импорт компонента
Для того, чтобы использовать компонент product-display, нам необходимо импортировать его в index.html.

📄 index.html
<!-- Import Components -->
<script src="./components/ProductDisplay.js"></script>

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

📄 index.html
<div id="app">
  <div class="nav-bar"></div>

  <div class="cart">Cart({{ cart }})</div>
  <product-display></product-display>
</div>

Если мы проверим это в браузере, мы увидим, что все по-прежнему отображается так же, как и раньше, но теперь, когда мы все переставили, кнопка "Добавить в корзину" ("Add to Cart") больше не увеличивает корзину. Мы исправим это на следующем уроке.

На данный момент, чтобы показать вам, насколько полезными могут быть эти повторно используемые блоки кода, я собираюсь добавить еще два компонента отображения продукта (product-display).

📄 index.html
<div id="app">
  <div class="nav-bar"></div>

  <div class="cart">Cart({{ cart }})</div>
  <product-display></product-display>
  <product-display></product-display>
  <product-display></product-display>
</div>

Когда мы обновим браузер, мы увидим, что все они отображаются. Каждый из них функционирует независимо.
les09_pic04.jpg
Пропсы
Теперь, когда мы начинаем изучать, как инкапсулировать повторно используемый код в эти компоненты, что происходит, когда нашему компоненту нужно что-то, что находится вне его самого? Например, что, если у родителя, так сказать, были какие-то данные о сообщении (message), и ребенок нуждался в них? Поскольку компонент имеет свою собственную изолированную область действия, он не может выйти за пределы самого себя, чтобы захватить что-то, что находится за пределами его области действия.
les09_pic05.jpg
The answer here is props. These are custom attributes for passing data into a component. They function kind of like a funnel, into which you can pass the data the component needs.

Ответ здесь - пропсы (props). Это пользовательские атрибуты для передачи данных в компонент. Они функционируют как воронка, в которую вы можете передавать данные, необходимые компоненту.
les09_pic06.jpg
Давайте добавим возможность для нашего компонента отображения продукта product-display использовать проп (реквизит, prop).

Внедрение в наш компонент проп (реквизит, prop).
Давайте дадим нашему рутовому приложению Vue, расположенному в main.js, новое свойство данных, которое указывает, был ли пользователь пользователем премиум-класса или нет (premium).

📄 main.js
const app = Vue.createApp({
  data() {
    return {
      cart: 0,
      premium: true
    }
  }
})

Если пользователь премиум-класса (premium), его доставка будет бесплатной. Поэтому нашему компоненту отображения продукта (product-display) необходим доступ к этим данным. Другими словами, ему нужен пользовательский атрибут (воронка), в который мы можем передавать эти данные. Давайте добавим это сейчас, и это мы сделаем, предоставив компоненту опцию пропсов (props) и добавив к нему проп (prop) под названием премиум (premium).

📄 components/ProductDisplay.js
app.component('product-display', {
  props: {
    premium: {
      type: Boolean,
      required: true
    }
  },
  ...
}

Обратите внимание, что свойство пропсов Vue имеет встроенную валидацию, поэтому мы можем указать такие вещи, как тип реквизита (type), обязателен ли он (required) и т.д.

📄 index.html
<div id="app">
  <div class="nav-bar"></div>

  <div class="cart">Cart({{ cart }})</div>
  <product-display :premium="premium"></product-display>
</div>

les09_pic07.jpg
Обратите внимание, как мы используем сокращение для v-bind, чтобы мы могли реактивно получать новое значение premium, если оно обновляется (от true до false).

Используя проп
Теперь, когда наш компонент отображения продукта product-display имеет премиальный проп premium, мы можем использовать его внутри компонента. Помните, мы хотим использовать тот факт, является ли пользователь премиум-классом или нет, чтобы определить, сколько ему нужно заплатить за доставку.

В шаблоне компонента мы добавим:

📄 components/ProductDisplay.js
template: 
  /*html*/
  `<div class="product-display">
    ...
      <p>Shipping: {{ shipping }}</p>
    ...
  </div>`,

Здесь shipping (доставка) это имя нового вычисляемого свойства компонента отображения продукта (product-display), которое выглядит следующим образом:

📄 components/ProductDisplay.js
computed: {
  ...
  shipping() {
    if (this.premium) {
      return 'Free'
    }
      return 2.99
    }
}

Вычисляемое свойство проверяет, является ли премиум проп (premium) истинным (true), и если да, возвращает 'Free' ("Бесплатно"). В противном случае он возвращает 2.99

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

Вот, что надо сделать.

Создайте новый компонент с именем 'product-details', который получал бы details через проп details.

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

Чтобы перейти на ветку с файлами окончания урока 9 выполните команду:

git checkout L9-end

Код файла index.html данного урока

    <div id="app">
      <div class="nav-bar"></div>

      <div class="cart">Cart({{ cart }})</div>
      <product-display :premium="premium"></product-display>
    </div>

    <!-- Import App -->
    <script src="./main.js"></script>

    <!-- Import Components -->
    <script src="./components/ProductDisplay.js"></script>
    <!-- solution -->
    <script src="./components/ProductDetails.js"></script>
    <!-- solution -->

    <!-- Mount App -->
    <script>
      const mountedApp = app.mount('#app')
    </script>


Код файла main.js данного урока

const app = Vue.createApp({
    data() {
        return {
            cart: 0,
            premium: true
        }
    },
    methods: {}
})



Код файла ProductDisplay.js данного урока

См здесь: Код файла ProductDisplay.js данного урока



Код файла ProductDetails.js данного урока

См здесь: Код файла ProductDetails.js данного урока



Результат выполнения данного урока

Cart({{ cart }})


Примечание переводчика

1. Я слегка меняю контент оригинальных файлов курса для тренировки, проверки русификации и т.д.

2. Код для HTML я размещаю в этом файле между тегами body.

3. Обратите внимание, чтобы в консоле браузера не было ошибок.

Теперь Нажмите здесь для перехода к следующему уроку ➳

... или ...


Ссылки по теме.