Подпишитесь на мою рассылку сейчас по адресу http://jauyeung.net/subscribe/.
Подпишитесь на меня в Twitter по адресу https://twitter.com/AuMayeung
Слоты - это полезная функция Vue.js, которая позволяет вам разделять различные части компонента в единое целое. Когда ваш компонент разделен на слоты, вы можете повторно использовать компоненты, поместив их в определенные вами слоты. Это также делает ваш код более чистым, поскольку позволяет отделить макет от логики приложения.
Кроме того, если вы используете слоты, вам больше не нужно составлять компоненты с родительскими дочерними отношениями, поскольку вы можете помещать любые компоненты в свои слоты.
Вот простой пример слотов Vue. Вы определяете свой слот в Layout.vue
файле:
<template> <div class="frame"> <slot
name="frame
"></slot> </div> </template>
Затем в другом файле вы можете добавить:
<Layout>
<template v-slot:frame><img src="an-image.jpg"> </template> </Layout>
Чтобы использовать слот в вашем Layout
компоненте.
Мы проясним приведенный выше пример, создав пример приложения. Чтобы проиллюстрировать использование слотов в Vue.js, мы создадим адаптивное приложение, которое отображает фрагменты статей из New York Times API и страницу поиска, где пользователи могут вводить ключевое слово для поиска в API.
В макете рабочего стола слева будет список названий элементов, а справа - фрагменты статей. Мобильный макет будет иметь раскрывающийся список для выбора отображаемого раздела и карточек с фрагментами статей под ним.
На странице поиска вверху будет форма поиска, а под ней - фрагменты статей, независимо от размера экрана.
Чтобы начать создание приложения, мы начнем с запуска Vue CLI. Мы бежим:
npx @vue/cli create nyt-app
для создания проекта Vue.js. Когда появится мастер, мы выбираем «Выбрать функции вручную». Затем мы решили включить в наш проект Vue Router и Babel.
Затем мы добавляем наши собственные библиотеки для стилизации и выполнения HTTP-запросов. Мы используем BootstrapVue для стилизации, Axios для выполнения запросов, VueFilterDateFormat для форматирования дат и Vee-Validate для проверки формы.
Для установки всех библиотек запускаем:
npm i axios bootstrap-vue vee-validate vue-filter-date-format
После того, как все библиотеки установлены, мы можем приступить к созданию нашего приложения.
Сначала мы используем слоты для создания макетов наших страниц. Создайте BaseLayout.vue
в папке components
и добавьте:
<template> <div> <div class="row"> <div class="col-md-3 d-none d-lg-block d-xl-none d-xl-block"> <slot name="left"></slot> </div> <div class="col"> <div class="d-block d-sm-none d-none d-sm-block d-md-block d-lg-none"> <slot name="section-dropdown"></slot> </div> <slot name="right"></slot> </div> </div> </div> </template> <script> export default { name: "BaseLayout" }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> </style>
В этом файле мы используем слоты Vue для создания адаптивного макета для домашней страницы. В этом файле есть слоты left
, right
и section-dropdown
. Слот left
отображается только при большом экране, поскольку мы добавили классы d-none d-lg-block d-xl-none d-xl-block
в слот left
. Слот section-dropdown
отображается только на маленьких экранах, поскольку мы добавили к нему классы d-block d-sm-none d-none d-sm-block d-md-block d-lg-none
. Эти классы являются адаптивными служебными классами из Bootstrap.
Полный список адаптивных служебных классов находится на https://getbootstrap.com/docs/4.0/utilities/display/.
Затем создайте файл SearchLayout.vue
в папке components
и добавьте:
<template> <div class="row"> <div class="col-12"> <slot name="top"></slot> </div> <div class="col-12"> <slot name="bottom"></slot> </div> </div> </template> <script> export default { name: "SearchLayout" }; </script>
чтобы создать еще один макет для нашей страницы поиска. У нас есть слоты top
и bottom
, занимающие всю ширину экрана.
Затем мы создаем папку mixins
и в ней создаем файл requestsMixin.js
и добавляем:
const axios = require("axios"); const APIURL = "https://api.nytimes.com/svc"; export const requestsMixin = { methods: { getArticles(section) { return axios.get( `${APIURL}/topstories/v2/${section}.json?api-key=${process.env.VUE_APP_API_KEY}` ); }, searchArticles(keyword) { return axios.get( `${APIURL}/search/v2/articlesearch.json?api-key=${process.env.VUE_APP_API_KEY}&q=${keyword}` ); } } };
создать миксин для выполнения HTTP-запросов к New York Times API. process.env.VUE_APP_API_KEY
- это ключ API для New York Times API, и мы получаем его из файла .env
в корневой папке проекта, где ключ переменной среды - VUE_APP_API_KEY
.
Затем в Home.vue
замените существующий код на:
<template> <div class="page"> <h1 class="text-center">Home</h1> <BaseLayout> <template v-slot:left> <b-nav vertical pills> <b-nav-item v-for="s in sections" :key="s" :active="s == selectedSection" @click="selectedSection = s; getAllArticles()" >{{s}}</b-nav-item> </b-nav> </template> <template v-slot:section-dropdown> <b-form-select v-model="selectedSection" :options="sections" @change="getAllArticles()" id="section-dropdown" ></b-form-select> </template> <template v-slot:right> <b-card v-for="(a, index) in articles" :key="index" :title="a.title" :img-src="(Array.isArray(a.multimedia) && a.multimedia.length > 0 && a.multimedia[a.multimedia.length-1].url) || ''" img-bottom > <b-card-text> <p>{{a.byline}}</p> <p>Published on: {{new Date(a.published_date) | dateFormat('YYYY.MM.DD hh:mm a')}}</p> <p>{{a.abstract}}</p> </b-card-text> <b-button :href="a.short_url" variant="primary" target="_blank">Go</b-button> </b-card> </template> </BaseLayout> </div> </template> <script> // @ is an alias to /src import BaseLayout from "@/components/BaseLayout.vue"; import { requestsMixin } from "@/mixins/requestsMixin"; export default { name: "home", components: { BaseLayout }, mixins: [requestsMixin], data() { return { sections: `arts, automobiles, books, business, fashion, food, health, home, insider, magazine, movies, national, nyregion, obituaries, opinion, politics, realestate, science, sports, sundayreview, technology, theater, tmagazine, travel, upshot, world` .split(",") .map(s => s.trim()), selectedSection: "arts", articles: [] }; }, beforeMount() { this.getAllArticles(); }, methods: { async getAllArticles() { const response = await this.getArticles(this.selectedSection); this.articles = response.data.results; }, setSection(ev) { this.getAllArticles(); } } }; </script> <style scoped> #section-dropdown { margin-bottom: 10px; } </style>
Мы используем слоты, определенные в BaseLayout.vue
в этом файле. В слот left
мы помещаем туда список имен разделов, чтобы отображать список слева, когда у нас есть экран размером с рабочий стол.
В слот section-dropdown
мы поместили раскрывающийся список, который отображается только на мобильных экранах, как определено в BaseLayout
.
Затем в слот right
мы помещаем карточки начальной загрузки для отображения фрагментов статей, как определено в BaseLayout
.
Мы помещаем все содержимое слота внутрь BaseLayout
и используем v-slot
вне элементов, которые мы хотим поместить в слоты, чтобы элементы отображались в назначенном слоте.
В разделе script
мы получаем статьи по разделам, определяя функцию getAllArticles
из requestsMixin
.
Затем создайте файл Search.vue
и добавьте:
<template> <div class="page"> <h1 class="text-center">Search</h1> <SearchLayout> <template v-slot:top> <ValidationObserver ref="observer" v-slot="{ invalid }"> <b-form @submit.prevent="onSubmit" novalidate id="form"> <b-form-group label="Keyword" label-for="keyword"> <ValidationProvider name="keyword" rules="required" v-slot="{ errors }"> <b-form-input :state="errors.length == 0" v-model="form.keyword" type="text" required placeholder="Keyword" name="keyword" ></b-form-input> <b-form-invalid-feedback :state="errors.length == 0">Keyword is required</b-form-invalid-feedback> </ValidationProvider> </b-form-group> <b-button type="submit" variant="primary">Search</b-button> </b-form> </ValidationObserver> </template> <template v-slot:bottom> <b-card v-for="(a, index) in articles" :key="index" :title="a.headline.main"> <b-card-text> <p>By: {{a.byline.original}}</p> <p>Published on: {{new Date(a.pub_date) | dateFormat('YYYY.MM.DD hh:mm a')}}</p> <p>{{a.abstract}}</p> </b-card-text> <b-button :href="a.web_url" variant="primary" target="_blank">Go</b-button> </b-card> </template> </SearchLayout> </div> </template> <script> // @ is an alias to /src import SearchLayout from "@/components/SearchLayout.vue"; import { requestsMixin } from "@/mixins/requestsMixin"; export default { name: "home", components: { SearchLayout }, mixins: [requestsMixin], data() { return { articles: [], form: {} }; }, methods: { async onSubmit() { const isValid = await this.$refs.observer.validate(); if (!isValid) { return; } const response = await this.searchArticles(this.form.keyword); this.articles = response.data.response.docs; } } }; </script> <style scoped> </style>
Это очень похоже на Home.vue
. Мы помещаем форму поиска в слот top
, помещая ее внутрь SearchLayour
, и помещаем содержимое нашего слота для слота top
, помещая нашу форму внутрь элемента <template v-slot:top>
.
Мы используем ValidationObserver
для проверки всей формы и ValidationProvider
для проверки ввода keyword
. Оба они предоставлены Vee-Validate.
После нажатия кнопки «Поиск» мы вызываем this.$refs.observer.validate();
для проверки формы. Мы получили this.$refs.observer
, так как ValidationObserver
мы вынесли за пределы формы.
Затем, как только проверка формы завершится успешно, this.$refs.observer.validate()
разрешив true
, мы вызываем searchArticles
из requestsMixin
для поиска статей.
В слот bottom
кладем карточки для вывода результатов поиска по статье. Он работает так же, как и другие слоты.
Далее в App.vue
мы помещаем:
<template> <div> <b-navbar toggleable="lg" type="dark" variant="info"> <b-navbar-brand href="#">New York Times App</b-navbar-brand> <b-navbar-toggle target="nav-collapse"></b-navbar-toggle> <b-collapse id="nav-collapse" is-nav> <b-navbar-nav> <b-nav-item to="/" :active="path == '/'">Home</b-nav-item> <b-nav-item to="/search" :active="path == '/search'">Search</b-nav-item> </b-navbar-nav> </b-collapse> </b-navbar> <router-view /> </div> </template> <script> export default { data() { return { path: this.$route && this.$route.path }; }, watch: { $route(route) { this.path = route.path; } } }; </script> <style> .page { padding: 20px; } </style>
чтобы мы добавили сюда BootstrapVue b-navbar
и наблюдали за изменением маршрута, чтобы мы могли установить опору active
на ссылку страницы, на которой в данный момент находится пользователь.
Затем мы меняем код main.js
на:
import Vue from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; import BootstrapVue from "bootstrap-vue"; import "bootstrap/dist/css/bootstrap.css"; import "bootstrap-vue/dist/bootstrap-vue.css"; import VueFilterDateFormat from "vue-filter-date-format"; import { ValidationProvider, extend, ValidationObserver } from "vee-validate"; import { required } from "vee-validate/dist/rules"; Vue.use(VueFilterDateFormat); Vue.use(BootstrapVue); extend("required", required); Vue.component("ValidationProvider", ValidationProvider); Vue.component("ValidationObserver", ValidationObserver); Vue.config.productionTip = false; new Vue({ router, store, render: h => h(App) }).$mount("#app");
Мы импортируем все используемые здесь пакеты для всего приложения, такие как BootstrapVue, Vee-Validate, а также виджеты для выбора календаря и даты и времени.
Стили также импортируются сюда, поэтому мы можем видеть их во всем приложении.
Затем в router.js
замените существующий код на:
import Vue from "vue"; import Router from "vue-router"; import Home from "./views/Home.vue"; import Search from "./views/Search.vue"; Vue.use(Router); export default new Router({ mode: "history", base: process.env.BASE_URL, routes: [ { path: "/", name: "home", component: Home }, { path: "/search", name: "search", component: Search } ] });
чтобы установить маршруты для нашего приложения, чтобы, когда пользователи вводят указанный URL-адрес или щелкают ссылку с ним, они могут видеть нашу страницу.
Наконец, мы заменяем код в index.html
на:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <title>New York Times App</title> </head> <body> <noscript> <strong >We're sorry but vue-slots-tutorial-app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
чтобы изменить название приложения.
Наконец, мы запускаем наше приложение, запустив npm run serve
в папке проекта нашего приложения, чтобы запустить наше приложение.
После этого вы должны увидеть: