This is a fork of https://github.com/learn-vuejs/vue-patterns
Полезные паттерны, методы, советы и рекомендации, а также тщательно подобранный список ссылок по Vue.
- Объявление компонентов
- Взаимодействие компонента
- Обработка событий компонента
- Условный рендеринг компонента
- Динамический компонент
- Композиция
- Передача входных параметров
- Компоненты высшего порядка (они же HOC)
- Внедрение зависимостей
- Обработка ошибок
- Советы по продуктивности
- Полезные ссылки
- Книга Fullstack Vue
Однофайловый компонент (сокращенно — SFC) — наиболее распространённый
<template>
<button class="btn-primary" @click.prevent="handleClick">
{{ text }}
</button>
</template>
<script>
export default {
data() {
return {
text: 'Нажми на меня',
};
},
methods: {
handleClick() {
console.log('Клик по кнопке');
},
},
}
</script>
<style scoped>
.btn-primary {
background-color: blue;
}
</style>
Vue.component('my-btn', {
template: `
<button class="btn-primary" @click.prevent="handleClick">
{{ text }}
</button>
`,
data() {
return {
text: 'Нажми на меня',
};
},
methods: {
handleClick() {
console.log('Клик по кнопке');
},
},
});
Vue.component('my-btn', {
data() {
return {
text: 'Нажми на меня',
};
},
methods: {
handleClick() {
console.log('Клик по кнопке');
},
},
render(h) {
return h('button', {
attrs: {
class: 'btn-primary'
},
on: {
click: this.handleClick,
},
});
},
});
Vue.component('my-btn', {
data() {
return {
text: 'Нажми на меня',
};
},
methods: {
handleClick() {
console.log('Клик по кнопке');
},
},
render() {
return (
<button class="btn-primary" onClick={this.handleClick}>
{{this.text}}
</button>
);
},
});
<template>
<button class="btn-primary" @click.prevent="handleClick">
{{ text }}
</button>
</template>
<script>
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default MyBtn extends Vue {
text = 'Нажми на меня';
handleClick() {
console.log('Клик по кнопке');
}
}
</script>
<style scoped>
.btn-primary {
background-color: blue;
}
</style>
- 🇷🇺 Официальная документация — Однофайловые компоненты
- 🇷🇺 Официальная документация — Render-функции и JSX
- 🇺🇸 7 способов определения шаблона компонента в VueJS
В целом, компонент Vue следует однонаправленному потоку данных, то есть входные параметры передаются вниз (см. официальное руководство), а события — наверх. Входные параметры — это данные только для чтения, поэтому невозможно изменить входные параметры дочерних компонентов. При изменении входных параметров, дочерние компоненты будут автоматически повторно отрендерены (входные параметры являются реактивными источниками данных). Дочерние компоненты могут генерировать событие только к непосредственному родительскому компоненту, так что он может изменять data
, сопоставляемые с props
дочернего компонента.
<template>
<button @click="$emit('click')">{{ text }}</button>
</template>
<script>
export default {
name: 'v-btn',
props: {
text: String,
},
};
</script>
<template>
<v-btn :text="buttonText" @click="handleClick"></v-btn>
</template>
<script>
export default {
data() {
return {
clickCount: 0,
buttonText: 'Стандартное название кнопки',
};
},
methods: {
handleClick() {
this.buttonText = `Кнопка нажата ${++this.clickCount}`;
console.log('Клик по кнопке', this.buttonText);
}
}
};
</script>
- 🇷🇺 Официальная документация — Входные параметры
- 🇺🇸 Паттерны взаимодействия компонента Vue.js
- 🇺🇸 Создание пользовательских полей ввода с помощью Vue.js
- 🇺🇸 Взаимодействие дочерних компонентов Vue
- 🇺🇸 Управление состоянием во Vue.js
- 🇺🇸 Взаимодействие во Vue.js, часть 2: родительский и дочерний компоненты
- 🇷🇺 Официальная документация — Пользовательские события
- 🇺🇸 Использование событий Vue для сокращения объявлений входных параметров
- 🇺🇸 Хуки компонента Vue.js как события
- 🇺🇸 Создание глобальной шины событий с помощью Vue.js
- 🇺🇸 Шина событий Vue.js + Промисы
v-if
<h1 v-if="true">Рендеринг только, если условие v-if равняется true</h1>
Использование v-if
и v-else
<h1 v-if="true">Рендеринг только, если условие v-if равняется true</h1>
<h1 v-else>Рендеринг только, если условие v-if равняется false</h1>
Использование v-else-if
<div v-if="type === 'A'">Рендеринг только, если `type` равняется `A`</div>
<div v-else-if="type === 'B'">Рендеринг только, если `type` равняется `B`</div>
<div v-else-if="type === 'C'">Рендеринг только, если `type` равняется `C`</div>
<div v-else>Рендеринг если `type` не равен ни `A`, ни `B`, ни `C`</div>
Использование v-show
<h1 v-show="true">Всегда рендерится, но виден только в том случае, если условия `v-show` равняются true</h1>
Если вы хотите по условию отобразить более одного элемента, вы можете использовать директивы (v-if
/ v-else
/ v-else-if
/v-show
) на элементе <template>
. Обратите внимание, что элемент <template>
фактические не будет отображаться в DOM. Это как невидимая обёртка.
<template v-if="true">
<h1>Все элементы</h1>
<p>будут отрендерены в DOM,</p>
<p>за исключением элемента `template`</p>
</template>
Если вы используете JSX в своем Vue-приложении, то можете применять все техники, например использования выражения if else
и switch case
, а также тернарные и логические операторы.
Использование выражения if else
export default {
data() {
return {
isTruthy: true,
};
},
render(h) {
if (this.isTruthy) {
return <h1>Рендеринг, если значение равно true</h1>;
} else {
return <h1>Рендеринг, если значение равно false</h1>;
}
},
};
Использование выражения switch case
import Info from './Info';
import Warning from './Warning';
import Error from './Error';
import Success from './Success';
export default {
data() {
return {
type: 'error',
};
},
render(h) {
switch (this.type) {
case 'info':
return <Info text={text} />;
case 'warning':
return <Warning text={text} />;
case 'error':
return <Error text={text} />;
default:
return <Success text={text} />;
}
},
};
Или можно использовать сопоставление с помощью объекта для упрощения выражений switch case
import Info from './Info';
import Warning from './Warning';
import Error from './Error';
import Success from './Success';
const COMPONENT_MAP = {
info: Info,
warning: Warning,
error: Error,
success: Success,
};
export default {
data() {
return {
type: 'error',
};
},
render(h) {
const Comp = COMPONENT_MAP[this.type || 'success'];
return <Comp />;
},
};
Использование тернарного оператора
export default {
data() {
return {
isTruthy: true,
};
},
render(h) {
return (
<div>
{this.isTruthy ? (
<h1>Рендеринг, если значение равно true</h1>
) : (
<h1>Рендеринг, если значение равно false</h1>
)}
</div>
);
},
};
Использование логического оператора
export default {
data() {
return {
isLoading: true,
};
},
render(h) {
return <div>{this.isLoading && <h1>Загрузка ...</h1>}</div>;
},
};
<component :is="currentTabComponent"></component>
В приведённом выше примере отрендеренный компонент будет уничтожаться, если другой компонент должен будет рендериться в <component>
. Если необходимо, чтобы компоненты сохраняли свои экземпляры без их уничтожения в теге <component>
, можно обернуть <component>
в тег <keep-alive>
:
<keep-alive>
<component :is="currentTabComponent"></component>
</keep-alive>
- 🇷🇺 Официальная документация — Динамические компоненты
- 🇷🇺 Официальная документация — Динамические и асинхронные компоненты
- 🇺🇸 Шаблоны динамических компонентов с Vue.js
<template>
<div class="component-b">
<component-a></component-a>
</div>
</template>
<script>
import ComponentA from './ComponentA';
export default {
components: {
ComponentA,
},
};
</script>
Если вы хотите расширить один Vue-компонент, можно поступить следующим образом:
<template>
<button class="button-primary" @click.prevent="handleClick">
{{buttonText}}
</button>
</template>
<script>
import BaseButton from './BaseButton';
export default {
extends: BaseButton,
props: ['buttonText'],
};
</script>
// closableMixin.js
export default {
props: {
isOpen: {
default: true
}
},
data: function() {
return {
shown: this.isOpen
}
},
methods: {
hide: function() {
this.shown = false;
},
show: function() {
this.shown = true;
},
toggle: function() {
this.shown = !this.shown;
}
}
}
<template>
<div v-if="shown" class="alert alert-success" :class="'alert-' + type" role="alert">
{{ text }}
<i class="pull-right glyphicon glyphicon-remove" @click="hide"></i>
</div>
</template>
<script>
import closableMixin from './mixins/closableMixin';
export default {
mixins: [closableMixin],
props: ['text']
};
</script>
<template>
<button class="btn btn-primary">
<slot></slot>
</button>
</template>
<script>
export default {
name: 'VBtn',
};
</script>
<template>
<v-btn>
<span class="fa fa-user"></span>
Логин
</v-btn>
</template>
<script>
import VBtn from './VBtn';
export default {
components: {
VBtn,
}
};
</script>
- 🇷🇺 Официальная документация — Содержимое слотов
- 🇺🇸 Понимание слотов компонентов с помощью Vue.js
- 🇺🇸 Составление пользовательских элементов с помощью слотов и именованными слотами
- 🇺🇸 Написание абстрактных компонентов во Vue.js (🇷🇺 перевод)
BaseLayout.vue
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
App.vue
<base-layout>
<template slot="header">
<h1>Здесь может быть заголовок страницы</h1>
</template>
<p>Абзац для основного контента.</p>
<p>И еще один.</p>
<template slot="footer">
<p>Здесь некоторые контактные данные</p>
</template>
</base-layout>
<template>
<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
<!-- У нас есть слот для каждого todo, передавая его -->
<!-- в объект `todo` в виде входного параметра для слота. -->
<slot v-bind:todo="todo">
{{ todo.text }}
</slot>
</li>
</ul>
</template>
<script>
export default {
name: 'TodoList',
props: {
todos: {
type: Array,
default: () => ([]),
}
},
};
</script>
<template>
<todo-list v-bind:todos="todos">
<template slot-scope="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
</template>
<script>
import TodoList from './TodoList';
export default {
components: {
TodoList,
},
data() {
return {
todos: [
{ todo: 'todo 1', isComplete: true },
{ todo: 'todo 2', isComplete: false },
{ todo: 'todo 3', isComplete: false },
{ todo: 'todo 4', isComplete: true },
];
};
},
};
</script>
- 🇷🇺 Официальная документация — Слоты с ограниченной областью видимости
- 🇺🇸 Разбираемся со слотами с ограниченной областью видимости
- 🇺🇸 Понимание слотов с ограниченной областью видимости во Vue.js
- 🇺🇸 Слоты с ограниченной областью видимости компонента во Vue.js
- 🇺🇸 Трюк к пониманию слотов с ограниченной областью видимости во Vue.js
- 🇺🇸 Мощность слотов в Vue
- 🇺🇸 Создание компонента, управляемого с клавиатуры, списка с помощью Vue.js и слотов с ограниченной областью видимости
В большинстве случаев вы можете использовать слоты с ограниченной областью видимости вместо рендеринга входных параметров. Но в некоторых случаях это может быть полезно.
С однофайловым компонентом SFC
<template>
<div id="app">
<Mouse :render="__render" />
</div>
</template>
<script>
import Mouse from "./Mouse.js";
export default {
name: "app",
components: {
Mouse
},
methods: {
__render({ x, y }) {
return (
<h1>
Позиция мыши ({x}, {y})
</h1>
);
}
}
};
</script>
<style>
* {
margin: 0;
height: 100%;
width: 100%;
}
</style>
С использованием JSX
const Mouse = {
name: "Mouse",
props: {
render: {
type: Function,
required: true
}
},
data() {
return {
x: 0,
y: 0
};
},
methods: {
handleMouseMove(event) {
this.x = event.clientX;
this.y = event.clientY;
}
},
render(h) {
return (
<div style={{ height: "100%" }} onMousemove={this.handleMouseMove}>
{this.$props.render(this)}
</div>
);
}
};
export default Mouse;
- 🇷🇺 Официальная документация — Render Functions & JSX
- 🇺🇸 Использование рендеринга входных параметров в Vue
- 🇺🇸 Использование рендеринга входных параметров Vue.js!
Иногда вам может понадобиться передать входные параметры и обработчики дочернему компоненту, не объявляя всех входных параметров дочернего компонента. Вы можете привязать $attrs
и $listeners
в дочернем компоненте и установить inheritAttrs
на false
(в противном случае div
и child-component
получат атрибуты).
<template>
<div>
<h1>{{title}}</h1>
<child-component v-bind="$attrs" v-on="$listeners"></child-component>
</div>
</template>
<script>
export default {
name: 'PassingPropsSample'
inheritAttrs: false,
props: {
title: {
type: String,
default: 'Привет, Vue!'
}
}
};
</script>
Из родительского компонента вы можете сделать следующее:
<template>
<passing-props-sample
title="Привет, передача входных параметров"
childPropA="Эти реквизиты будут правильно сопоставлены c <child-component />"
@click="handleChildComponentClick"
>
</passing-props-sample>
</template>
<script>
import PassingPropsSample from './PassingPropsSample';
export default {
components: {
PassingPropsSample
},
methods: {
handleChildComponentClick() {
console.log('Клик по кнопке в дочернем компоненте');
}
}
};
</script>
- 🇺🇸 Компоненты высшего порядка Vue.js
- 🇺🇸 Нужны ли нам компоненты высшего порядка порядка во Vue.js?
- 🇺🇸 Компоненты высшего порядка во Vue.js
Vue поддерживает механизм предоставления и внедрения объекта во всех потомки, независимо от глубины иерархии компонентов, при условии, что компоненты находятся в одной и той же цепочке родителей. Обратите внимание, что привязки provide
и inject
не являются реактивными, пока вы не передадите наблюдаемый объект.
<parent-component>
<child-component>
<grand-child-component></grand-child-component>
</child-component>
</parent-component>
С приведенной выше иерархией компонентов в качестве примера для получения данных из parent-component
вам нужно передавать данные (объект) в качестве props
компоненту child-component
и компоненту grand-child-component
. Однако, если parent-component
предоставляет (provide
) данные (объект), grand-child-component
может просто определить свойство inject
для получения объекта, предоставляемого parent-component
.
- 🇷🇺 Официальный API
- 🇷🇺 Официальное руководство
- 🇺🇸 Взаимодействие компонента
- 🇺🇸 Внедрение зависимостей в приложении Vue.js с TypeScript
// ParentComponent.vue
export default {
provide: {
theme: {
primaryColor: 'blue',
},
},
};
// GrandChildComponent.vue
<template>
<button :style="{ backgroundColor: primary && theme.primaryColor }">
<slot></slot>
</button>
</template>
<script>
export default {
inject: ['theme'],
props: {
primary: {
type: Boolean,
default: true,
},
},
};
</script>
// ParentComponent.vue
import { Component, Vue, Provide } from 'vue-property-decorator';
@Component
export class ParentComponent extends Vue {
@Provide
theme = {
primaryColor: 'blue',
};
}
// GrandChildComponent.vue
<template>
<button :style="{ backgroundColor: primary && theme.primaryColor }">
<slot></slot>
</button>
</template>
<script>
import { Component, Vue, Inject, Prop } from 'vue-property-decorator';
export class GrandChildComponent extends Vue {
@Inject() theme;
@Prop({ default: true })
primary: boolean;
};
</script>
export default {
name: 'ErrorBoundary',
data() {
return {
error: false,
errorMessage: '',
};
},
errorCaptured (err, vm, info) {
this.error = true;
this.errorMessage = `${err.stack}\n\nобнаружена в методе ${info} компонента`;
return false;
},
render (h) {
if (this.error) {
return h('pre', { style: { color: 'red' }}, this.errorMessage);
}
return this.$slots.default[0]
}
};
<error-boundary>
<another-component/>
</error-boundary>
Наблюдение при создании
// Не делайте так
created() {
this.fetchUserList();
},
watch: {
searchText: 'fetchUserList',
}
// Делайте так
watch: {
searchText: {
handler: 'fetchUserList',
immediate: true,
}
}
- 🇺🇸 Рефакторинг Vue: очистка списка сообщений с лучшим разделением компонентов и с большим количеством ES6
- 🇺🇸 Очистка модулей Vue с помощью стрелочных функций ES6
- 🇺🇸 Примеры чистого кода Vue
- 🇺🇸 Оптимизация производительности с помощью вычисляемых свойств
- 🇺🇸 Упрощение компонентов с помощью вычисляемых сеттеров
- 🇺🇸 Разделение модулей Vuex с паттерном «Посредник»
- 🇺🇸 Геттеры Vuex великолепны, но не злоупотребляйте ими
- 🇺🇸 Повторное использование функций-мутаций Vuex
- 🇺🇸 Паттерн для обработки AJAX-запросов в Vuex
- 🇺🇸 Одиночные изменения мутацией Vuex vs. принципа единственной ответственности
- 🇺🇸 Компоненты и способы взаимодействия в Vue и Vuex
- 🇺🇸 Почему VueX — идеальный интерфейс между фронтендом и API
- 🇺🇸 Композиция действий с Vuex
- 🇺🇸 Как создавать сложные, крупномасштабные приложения Vue.js с Vuex
- 🇺🇸 Должен ли я хранить данные в Vuex?
- 🇺🇸 В общем, это как использовать v-model с Vuex. Вычисляемый сеттер в действии.
- 🇺🇸 Компоненты без рендеринга во Vue.js (перевод)
- 🇺🇸 Создание компонентов без рендеринга для обработки CRUD-операций во Vue.js
- 🇺🇸 Как улучшить рабочий процесс с помощью консоли JavaScript (перевод)
- 🇺🇸 Как структурировать проект Vue.js
- 🇺🇸 Крупномасштабная структура приложения Vuex
- 🇺🇸 Структура приложения Vue.js и архитектура CSS
- 🇺🇸 Как создать Vue-компоненты, как и профессионал 😎
- [🇺🇸 4 совета по работе с Vue.js(https://itnext.io/four-tips-for-working-with-vue-js-b362d97de852) (перевод)
- 🇺🇸 Советы для непритязательного разработчика VueJS
- 🇺🇸 Throttle и debounce событий с помощью Vue и lodash
- 🇺🇸 Возможны ли частично применимые функции в обработчиках событий?
- 🇺🇸 Vue.js — соображения и трюки (перевод)
- 🇺🇸 Шесть случайных пробел и их решения в VueJS
- 🇺🇸 Когда VueJS не может помочь вам
- 🇺🇸 То, что не будет работать с использованием Vue
- 🇺🇸 Трюк#15 Отложенное выполнение с _.debounce
- 🇺🇸 Крис Фриц (Chris Fritz) - Антипаттерны Vue.js (и как их избежать)
- 🇺🇸 Распространённые ошибки, которые следует избегать при работе с Vue.js
- 🇺🇸 Избегайте этого распространённого антипаттерна в приложениях с полным стеком Vue / Laravel
- 🇺🇸 [Видео] - VueNYC - Три запаха кода Vue, и что вы можете с ними поделать- Matt Rothenberg (@mattrothenberg)
- 🇺🇸 81: Evan You - Продвинутый дизайн Vue-компонента
- 🇺🇸 7 секретных шаблонов, про которые Vue-консультанты не хотели бы, чтобы вы о них знали
- 🇺🇸 Vue + TypeScript: A Match Made in Your Code Editor
- 🇺🇸 Написание компонентов на основе классов с помощью Vue.js и TypeScript
- 🇺🇸 Создание Vue-компонента Interpose на основе реализации из React
- 🇺🇸 Составление вычисляемых свойств в Vue.js
- 🇺🇸 4 AJAX-паттерна для приложений Vue.js
- 🇺🇸 3 паттерна разделения кода для VueJS и Webpack
- 🇺🇸 Самый простой способ улучшить ваше приложение Vue.js. Часть 1
- 🇺🇸 Использование JSX с Vue и почему вам должно быть не всё равно
- 🇺🇸 Составные компоненты
- 🇺🇸 Создание многоуровневых компонентов Vue.js
- 🇺🇸 Понимание реактивности Vue.js в подробностях с помощью Object.defineProperty()
- 🇺🇸 Шаблонизация в Vue: разделение проблем или разделение технологий или что-то еще?
- 🇺🇸 Хранение данных компонентов Vue
- 🇺🇸 Создание многоразовых переходов во Vue
- 🇺🇸 vue-advanced-workshop
- 🇺🇸 Сделайте элегантно: Как создать пользовательские интерфейсы, основанные на данных во Vue
- 🇺🇸 Создание экземпляров компонентов Vue.js программным путём
- 🇺🇸 Управление разрешениями пользователей в приложении Vue.js
- 🇺🇸 Рендеринг функциональных компонентов во Vue.js
- 🇺🇸 Проход по свойствам объекта
- 🇺🇸 Отмена асинхронных операций в Vue.js
- 🇺🇸 Стили с ограниченной областью видимости с помощью v-html
- 🇺🇸 Постраничная навигация с помощью с Vuejs
- 🇺🇸 Функция render() — что такое аргумент h (перевод)
- 🇺🇸 Как писать Vue-компоненты, которые хорошо взаимодействуют (перевод)
- 🇺🇸 Создание адаптивных компонентов Vue с помощью ResizeObserver
- 🇺🇸 Обязательное руководство по формам во Vue.js
- 🇺🇸 Хороший, спорный, злой Vue.js (перевод)
- 🇺🇸 Динамические компоненты шаблона Vue.js
- 🇺🇸 Продвинутые концепты Vue.js: примиси, пользовательские директивы, фильтры, переходы и управление состоянием
- 🇺🇸 Введение паттерна одиночного элемента
- 🇺🇸 Управление DOM за пределами вашего приложения Vue.js с помощью portal-vue
- 🇺🇸 Добавление i18n и управление переводами сайта на Vue.js
- 🇺🇸 Управление сложными ожиданиями в пользовательских веб-интерфейсах