Abr@X@bra.ru
Использование выделенных слотов в Vue.js
Использование выделенных слотов в Vue.js

Использование выделенных слотов в Vue.js

23.09.2018
399
Начнем с краткого введения в концепцию слотов Vue.js. Слоты полезны, когда вы хотите выводить контент в определенное место компонента. 

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


Существует три типа слотов:

default / unnamed slots: используется, когда у вас есть один слот в компоненте. Мы создаем их, добавляя <template> в шаблон, где мы хотим внедрить наш контент. Этот тег <slot> будет заменен любым контентом, переданным шаблону компонента.

named slots: используются, когда у вас есть несколько слотов в компоненте, и мы хотим вводить разные материалы в разные места (слоты). Мы создаем их, добавляя <slot> с атрибутом имени (например, <slot name = "header"> </ slot>). Затем, когда мы визуализируем наш компонент, мы предоставляем содержимое слота для каждого именованного слота, добавляя атрибут с именем.

<base-layout>
  <template slot="header">
    <h1>My awsome header</h1>
  </template>
  <template slot="footer">
    <p>My awsome footer</p>
  </template>
</base-layout>

Таким образом, теги <slot> в компоненте будут заменены содержимым, переданным компоненту.

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



Представьте себе компонент, который настраивает и подготавливает внешний API для использования в другом компоненте, но не тесно связан с каким-либо конкретным шаблоном. Такой компонент можно было бы повторно использовать в нескольких местах, создавая разные шаблоны, но используя один и тот же базовый объект с конкретным API.

Я создал компонент (GoogleMapLoader.vue), который:

  1. инициализирует API Карт Google
  2. создает объекты google и map
  3. предоставляет эти объекты родительскому компоненту, в котором используется GoogleMapLoader
Ниже приведен пример того, как это можно достичь. Мы проанализируем код по частям и посмотрим, что на самом деле происходит в следующем разделе.

Давайте сначала создадим наш шаблон GoogleMapLoader.vue:

<template>
  <div>
    <div class="google-map" data-google-map></div>
    <template v-if="Boolean(this.google) && Boolean(this.map)">
      <slot :google="google" :map="map" />
    </template>
  </div>
</template>

Теперь наш скрипт должен передать некоторые props для компонента, который позволяет нам установить API Карт Google и Map:

import GoogleMapsApiLoader from "google-maps-api-loader";

export default {
  props: {
    mapConfig: Object,
    apiKey: String
  },
  data() {
    return {
      google: null,
      map: null
    };
  },
  async mounted() {
    const googleMapApi = await GoogleMapsApiLoader({
      apiKey: this.apiKey
    });
    this.google = googleMapApi;
    this.initializeMap();
  },
  methods: {
    initializeMap() {
      const mapContainer = this.$el.querySelector("[data-google-map]");
      this.map = new this.google.maps.Map(mapContainer, this.mapConfig);
    }
  }
};

Создайте компонент, который инициализирует нашу карту

В шаблоне мы создаем контейнер для карты, который будет использоваться для монтирования объекта Map, извлеченного из API Карт Google.

// GoogleMapLoader.vue
<template>
  <div>
    <div class="google-map" data-google-map></div>
  </div>
</template>

Далее, наш скрипт должен получить props из родительского компонента, который позволит нам установить карту Google. Эти props состоят из:

  1. mapConfig: объект конфигурации Google Maps
  2. apiKey: наш персональный ключ api, требуемый Google Maps
// GoogleMapLoader.vue
import GoogleMapsApiLoader from "google-maps-api-loader";

export default {
  props: {
    mapConfig: Object,
    apiKey: String
  },

Затем мы устанавливаем начальные значения google и map равными null:

data() {
  return {
    google: null,
    map: null
  };
},

Мы создаем экземпляр Google Maps Api и объект карты из него. Нам также необходимо установить значения google и сопоставить созданные экземпляры:

async mounted() {
  const googleMapApi = await GoogleMapsApiLoader({
    apiKey: this.apiKey
  });
  this.google = googleMapApi;
  this.initializeMap();
},
methods: {
  initializeMap() {
    const mapContainer = this.$el.querySelector("[data-google-map]");
    this.map = new this.google.maps.Map(mapContainer, this.mapConfig);
  }
}
};

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

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

Для этого нам нужно разрешить родительскому компоненту использовать наш GoogleMapLoader для доступа к this.google и this.map, которые установлены внутри компонента GoogleMapLoader.  Области с ограниченным доступом позволяют нам отображать свойства, установленные в дочернем компоненте, в родительский компонент. 

Компонент, который использует наш компонент инициализатора

В шаблоне мы отображаем компонент GoogleMapLoader и передаем props, необходимые для инициализации карты.

// TravelMap.vue
<template>
  <GoogleMapLoader
    :mapConfig="mapConfig"
    apiKey="yourApiKey"
  />
</template>

Наш скрипт должен выглядеть так:

import GoogleMapLoader from "./GoogleMapLoader";
import { mapSettings } from "@/constants/mapSettings";

export default {
  components: {
    GoogleMapLoader,
  },
  computed: {
    mapConfig() {
      return {
        ...mapSettings,
        center: { lat: 0, lng: 0 }
      };
    },
  }
};

Все еще нет доступных слотов, поэтому давайте добавим их.

Экспортируем свойства google и map в родительский компонент, добавив слот

Наконец, мы можем добавить слот с областью видимости, который позволит нам получить доступ к props дочерних компонентов в родительском компоненте. Мы делаем это, добавляя тэг <slot> в дочерний компонент и передавая props, которые мы хотим выставить (с помощью директивы v-bind). Оно не отличается от передачи props до дочернего компонента, но его выполнение в теге <slot> приведет к изменению направления потока данных.

// GoogleMapLoader.vue
<template>
  <div>
    <div class="google-map" data-google-map></div>
    <template v-if="Boolean(this.google) && Boolean(this.map)">
      <slot
        :google="google"
        :map="map"
      />
    </template>
  </div>
</template>

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

Получаем открытые props в родительском компоненте с использованием атрибута slot-scope

Чтобы получить props в родительском компоненте, мы объявляем элемент шаблона и используем атрибут slot-scope. Этот атрибут имеет доступ к объекту, несущему все props, из дочернего компонента. Мы можем захватить весь объект, или мы можем деструктурировать этот объект и только то, что нам нужно.

// TravelMap.vue
<template>
  <GoogleMapLoader
    :mapConfig="mapConfig"
    apiKey="yourApiKey"
  >
    <template slot-scope="{ google, map }">
      {{ map }}
      {{ google }}
    </template>
  </GoogleMapLoader>
</template>

Несмотря на то, что реквизиты google и map не существуют в области TravelMap, компонент имеет к ним доступ, и мы можем использовать их в шаблоне.

Да, хорошо, но зачем мне это делать? Какая польза от всего этого?

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

Создание компонентов для Markers и Polylines

Теперь, когда у нас будет готовая карта, мы создадим два компонента, которые будут использоваться для добавления элементов в TravelMap.

// GoogleMapMarker.vue
import { POINT_MARKER_ICON_CONFIG } from "@/constants/mapSettings";

export default {
  props: {
    google: {
      type: Object,
      required: true
    },
    map: {
      type: Object,
      required: true
    },
    marker: {
      type: Object,
      required: true
    }
  },
  mounted() {
    new this.google.maps.Marker({
      position: this.marker.position,
      marker: this.marker,
      map: this.map,
      icon: POINT_MARKER_ICON_CONFIG
    });
  },
};

// GoogleMapLine.vue
import { LINE_PATH_CONFIG } from "@/constants/mapSettings";

export default {
  props: {
    google: {
      type: Object,
      required: true
    },
    map: {
      type: Object,
      required: true
    },
    path: {
      type: Array,
      required: true
    }
  },
  mounted() {
    new this.google.maps.Polyline({
      path: this.path,
      map: this.map,
      ...LINE_PATH_CONFIG
    });
  },
};

Оба из них получают google, которые мы используем для извлечения требуемого объекта (Marker или Polyline), а также карты, которая дает ссылку на карту, на которую мы хотим поместить наш элемент.

Каждый компонент также ожидает дополнительной поддержки для создания соответствующего элемента. В этом случае мы имеем маркер и путь соответственно.

На данном шаге мы создаем элемент (Marker / Polyline) и прикрепляем его к нашей карте, передавая свойство map в конструктор объекта.

Есть еще один шаг ...

Добавить элементы на карту

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

Нам также необходимо предоставить данные, необходимые самому элементу. В нашем случае это объект-маркер с положением маркера и объектом пути с координатами.

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

// TravelMap.vue
<template>
  <GoogleMapLoader
    :mapConfig="mapConfig"
    apiKey="yourApiKey"
  >
    <template slot-scope="{ google, map }">
      <GoogleMapMarker
        v-for="marker in markers"
        :key="marker.id"
        :marker="marker"
        :google="google"
        :map="map"
      />
      <GoogleMapLine
        v-for="line in lines"
        :key="line.id"
        :path.sync="line.path"
        :google="google"
        :map="map"
      />
    </template>
  </GoogleMapLoader>
</template>

Нам нужно импортировать необходимые компоненты в наш скрипт и установить данные, которые будут переданы маркерам и строкам:

import { mapSettings } from "@/constants/mapSettings";

export default {
  components: {
    GoogleMapLoader,
    GoogleMapMarker,
    GoogleMapLine
  },
  data() {
    return {
      markers: [
        { id: "a", position: { lat: 3, lng: 101 } },
        { id: "b", position: { lat: 5, lng: 99 } },
        { id: "c", position: { lat: 6, lng: 97 } }
      ],
      lines: [
        { id: "1", path: [{ lat: 3, lng: 101 }, { lat: 5, lng: 99 }] },
        { id: "2", path: [{ lat: 5, lng: 99 }, { lat: 6, lng: 97 }] }
      ]
    };
  },
  computed: {
    mapConfig() {
      return {
        ...mapSettings,
        center: this.mapCenter
      };
    },
    mapCenter() {
      return this.markers[1].position;
    }
  }
};

Готово!

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

Программирование на основе фреймворка Vue.js. Если Вам или Вашей компании необходим проект на Vue.js, пишите мне, если у меня есть свободное время для нового проект, я обязательно помогу Вам с реализацией данной задачи.

Email - [email protected]

Telegram - @abraxabr 





Vue.js, Slot
Читайте также:
Canvas – HTML5. Подробное руководство

Canvas – HTML5. Подробное руководство

Canvas с его обманчиво простым API может революционно преобразовать создание веб-приложений для всех устройств, а не тол...
Читать
GreenSock для начинающих: учебное пособие по веб-анимации (часть 1)

GreenSock для начинающих: учебное пособие по веб-анимации (часть 1)

Моя цель в этой статье - дать вам подробное введение в GreenSock, также известную как GSAP (GreenSock Animation Plat...
Читать
CSS Grid, подробное руководство по гридам с примерами

CSS Grid, подробное руководство по гридам с примерами

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