Что такое Vue.js. Первое приложение

Vue.js представляет современный прогрессивный фреймворк, написанный на языке JavaScript и предназначенный для создания веб-приложений клиентского уровня. Основная сфера применения данного фреймворка - это создание и организация пользовательского интерфейса. Первый релиз фреймворка увидел свет в феврале 2014 года. Его создателем является Эван Ю (Evan You), который до этого работал в Google над AngularJS. С тех пор фреймфорк динамично развивается.

Официальный сайт фреймворка - https://vuejs.org, где можно найти непосредственно файлы фреймворка, а также сопроводительные материалы и документацию. Кроме того, сам проект доступен на github по адресу https://github.com/vuejs/vue.

Vue.js имеет довольно небольшой размер - не более 20 кБ, и при этом обладает хорошей производительностью по сравнению с такими фреймворками как Angular или React. Поэтому неудивительно, что данный фреймворк в последнее время набирает обороты и становится все более популярным.

Одним из ключевых моментов в работе Vue.js является виртуальный DOM. Структура веб-страницы, как правило, описывается с помощью DOM (Document Object Model), которая представляет организацию элементов HTML на странице. Для взаимодействия с DOM (добавления, изменения, удаления html-элементов) применяется JavaScript. Но когда мы пытаемся манипулировать html-элементами с помощью JavaScript, то мы можем столкнуться со снижением производительности, особенно при изменении большого количества элементов. А операции над элементами могут занять некоторое время, что неизбежно скажется на пользовательском опыте. Однако если бы мы работали из кода JS с объектами JavaScript, то операции производились бы быстрее.

Для этого Vue.js использует виртуальный DOM. Виртуальный DOM представляет легковесную копию обычного DOM. Если приложению нужно узнать информацию о состоянии элементов, то происходит обращение к виртуальному DOM. Если данные, которые используются в приложении Vue.js, изменяются, то изменения вначале вносятся в виртуальный DOM. Потом Vue выбирает минимальный набор компонентов, для которых надо выполнить изменения на веб-странице, чтобы реальный DOM соответствовал виртуальному. Благодаря виртуальному DOM повышается производительность приложения.

Vue.js поддерживается всеми браузерами, которые совместимы с ECMAScript 5. На данный момент это все современные браузеры, в том числе IE11.

Первое приложение

Создадим первое приложение на Vue.js. Прежде всего нам надо подключить файлы фреймворка на веб-страницу. Все необходимые материалы для загрузки можно найти по адресу https://vuejs.org/v2/guide/installation.html. С этой страницы можно загрузить файл фреймворка локально (доступен в двух вариантах: Production и Development).

Вместо использования локального файла мы можем загружать фреймворк из CDN по ссылке: https://unpkg.com/vue

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

npm install vue

В данном случае будем использовать подключение файла из CDN. Для этого определим следующую веб-страницу:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <input type="text" v-on:input="setMessage">
        <p>{{ message }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                message: 'Hello, Vue!'
            },
            methods: {
                setMessage: function(event) {
                    this.message = event.target.value
                }
            }
        })
    </script>
</body>
</html>

Для создания объекта приложения в Vue.js применяется объект Vue. Этот объект, во-первых, определяет корневой элемент приложения на веб-странице с помощью параметра el:

el: '#app'

То есть корневым элементом приложения будет элемент с id равным app.

Также объект определяет используемые данные через параметр data:

data: {
    message: 'Hello, Vue!'
}

В данном случае определено только свойство message, которая хранит строку.

В элементе с id=app на веб-странице, используя двойные фигурные скобки мы можем вывести значение свойства message и более того связать участок веб-станицы с этим элементом.

<p>{{ message }}</p>

Последний параметр объекта Vue - methods определяет действия, которые выполняются в приложении:

methods: {
    setMessage: function(event) {
        this.message = event.target.value
    }
}

Здесь определен метод setMessage, который является обработчиком события ввода для элемента input на странице. Через параметр event в этот метод передается информация о событии. В частности, используя значение event.target.value, мы можем получить введенное пользователем значение и присвоить его переменной message. Для получения переменной message, которая была определена выше в параметра data, применяется ключевое слово this.

А для связи элемента input с этим методом определяется атрибут v-on:input="setMessage".

Теперь запустим веб-страницу в веб-браузере. При вводе в текстовое поле будет автоматически изменяться значение, которое выводится на веб-страницу.


Объект Vue

Для создания приложения в Vue.js используется объект Vue. При создании этого объекта в его конструктор передается параметр, с помощью которых производится инициализация приложения:

const vm = new Vue({
    // параметры
})

Касательно кода HTML приложение Vue.js представляет некоторую область на веб-странице. Для определения этой области на веб-станице можно определить какой-либо элемент. А для связи с объектом Vue в нем определяется параметр el:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app'
        })
    </script>
</body>
</html>

С помощью параметра data можно определить данные, которые должно хранить приложение, то есть его состояние:

const app = new Vue({
    el: '#app',
    data: {
        name: 'Tom',
        age: 25
    }
})

Здесь через параметр data определяются свойства name и age.

Данные можно определять в виде внешнего объекта и затем передавать параметру data:

const userData = {name: 'Tom', age: 25};

const app = new Vue({
    el: '#app',
    data: userData
});

app.age = 30;
app.name = 'Bob';

Кроме того, используя объект Vue, мы можем обращаться к его свойствам по имени, получать и присваивать им значение.

Методы

Кроме хранения состояния Vue может определять поведение в виде параметра methods. Этот параметр указывает на набор методов. Каждый метод представляет стандартную функцию JavaScript. Например, определим ряд методов:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <p>{{ name }}</p>
        <p>{{ welcome() }}</p>
        <p>Name: {{ displayName() }}</p>
        <p>Factorial of 5 is equal to {{ factorial(5) }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                name: 'Tom',
                age: 25
            },
            methods: {
                welcome: function() {
                    return "Welcome"
                },
                displayName: function() {
                    return this.name
                },
                factorial: function(n) {
                    let result = 1
                    for (let i=1; i<=n; i++) {
                        result = result * i
                    }
                    return result
                }
            }
        })
    </script>
</body>
</html>

Здесь определено три метода. Первый метод welcome просто возвращает некоторое значение. Второй метод возвращает значение свойства name. Для обращения к свойствам объекта Vue в методах используется ключевое слово this.

Третий метод принимает параметр - число и вычисляет для него факториал.


Привязка данных

Vue.js позволяет декларативным образом устанавливать привязку между элементами веб-страницы и данными объекта Vue. Есть различные формы привязки.

Интерполяция

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

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <p>{{ name }} - {{ age }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                name: 'Tom',
                age: 25
            }
        })
    </script>
</body>
</html>

При рендеринге выражения со скобками {{}} будут заменяться соответствующими значениями. При этом в интерполяции могут использоваться любые валидные выражения JavaScript.

Привязка к атрибутам

Для привязки к атрибутам html-элементов предназначена директива v-bind.

Для привязки к атрибуту после v-bind через двоеточие указывается непосредственно сам атрибут, к которому надо выполнять привязку.

Для привязки атрибутов также можно использовать сокращенную форму.

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <a v-bind:href="link">{{ text }}</a>
        <a :href="link" :target="target">{{ text }}</a>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                text: 'Google',
                link: 'https://google.com',
                target: '_blank'
            }
        })
    </script>
</body>
</html>

Однократная привязка

Если необходимо, чтобы значение было привязано к элементу HTML только один раз и впоследствии не изменялось, то применяется директива v-once:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <input type="text" v-on:input="setMessage" />
        <p v-once>{{ message }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                message: 'Hello, Vue!'
            },
            methods: {
                setMessage: function(event) {
                    this.message = event.target.value
                }
            }
        })
    </script>
</body>
</html>

Привязка к HTML

Для привязки элемента к коду HTML применяется директива v-html:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <div v-html="message"></div>
        <div>{{ message }}</div>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                message: '<h2>Hello, Vue!</h2>'
            }
        })
    </script>
</body>
</html>

С помощью директивы v-html привязываемое значение будет рассматриваться как код HTML. В то же время при простой интерполяции код HTML будет интерпретироваться как обычная строка.


Шаблоны

Для работы с веб-страницей Vue.js использует шаблоны. Шаблоны в Vue.js представляют валидную разметку с кодом HTML, которая компилируется в функции рендеринга виртуального DOM. Внутри шаблона может использоваться состояние приложения, то есть свойства объекта Vue, определенные через параметр data.

Возьмем следующую страницу:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <p>Name: {{ name }} Age: {{ age }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                name: 'Tom',
                age: 25
            }
        })
    </script>
</body>
</html>

Здесь все, что внутри элемента <div id="app">, представляет шаблон Vue. То есть в данном случае шаблоном является код:

<p>Name: {{ name }} Age: {{ age }}</p>

Vue.js управляет именно этой частью веб-страницы. Вне шаблона Vue не работает.

Также шаблон можно задать через параметр template в объекте Vue:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app"></div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                name: 'Tom',
                age: 25
            },
            template: '<p>Name: {{ name }} Age: {{ age }}</p>'
        })
    </script>
</body>
</html>

При задании шаблона через template элемент <div id="app"> не добавляется в структуру HTML и соответственно не отрисовывается.


Обработка событий

Для обработки событий элементов HTML в Vue.js используется директива v-on, после которой через двоеточие указывается тип события:

v-on:click="действия"

В качестве типа события используется любое стандартное событие элементов на веб-странице. Затем этой директиве в кавычках передаются те действия, которые должны выполниться при возникновении события. Например:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <button v-on:click="counter++">+</button>
        <button v-on:click="if (counter > 0) counter--">-</button>
        <div>{{ counter }}</div>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                counter: 0
            }
        })
    </script>
</body>
</html>

В качестве действия директиве v-on можно передать любые валидные операции JavaScript. В данном случае, так как у нас определено во Vue свойство counter, то мы можем манипулировать значением этого свойства. Фактически мы передаем директивам v-on кусок кода JavaScript, где свойства из Vue доступны как обычные переменные.

Также мы можем использовать сокращенный синтаксис для определения обработчиков событий:

<button @click="counter++">+</button>
<button @click="if (counter > 0) counter--">-</button>

Однако в случае более сложных операций такой подход не является оптимальным. И в этом случае, как правило, все операции, которые надо вызывать при возникновении события, выносятся в отдельные методы объекта Vue. Например, перепишем предыдущий пример:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <button @click="increase">+</button>
        <button @click="decrease">-</button>
        <div>{{ counter }}</div>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                counter: 0
            },
            methods: {
                increase: function() {
                    this.counter++
                },
                decrease: function() {
                    if (this.counter > 0) {
                        this.counter--
                    }
                }
            }
        })
    </script>
</body>
</html>

При нажатии на одну кнопку срабатывает метод increase, который увеличивает значение переменной counter. При нажатии на вторую кнопку вызывается метод decrease, который уменьшает это значение.

При генерации события в его обработчике в качестве параметра мы можем получить объект, который инкапсулирует всю информацию о событии:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <button @click="increase">+</button>
        <button @click="decrease">-</button>
        <div>{{ counter }}</div>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                counter: 0
            },
            methods: {
                increase: function(event) {
                    console.log(event)
                    this.counter++
                },
                decrease: function(event) {
                    console.log(event)
                    this.counter--
                }
            }
        })
    </script>
</body>
</html>

В качестве параметра в методы-обработчики события передается объект события. Это стандартный объект, который используется при обработке событий в обычном JavaScript. В зависимости от типа события этот объект может нести разную информацию.

Кроме того, мы можем передать в методы для обработки событий какие-то дополнительные значения:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <button @click="increase(3)">+</button>
        <button @click="decrease(2)">-</button>
        <div>{{ counter }}</div>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                counter: 0
            },
            methods: {
                increase: function(n) {
                    this.counter = this.counter + n
                },
                decrease: function(n) {
                    this.counter = this.counter - n
                }
            }
        })
    </script>
</body>
</html>

В данном случае увеличение переменной counter будет идти на 3, а уменьшение - на 2.

И также мы можем совместить передачу значений и объекта события:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <button @click="increase(3, $event)">+</button>
        <button @click="decrease(2, $event)">-</button>
        <div>{{ counter }}</div>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                counter: 0
            },
            methods: {
                increase: function(n, event) {
                    console.log(event)
                    this.counter = this.counter + n
                },
                decrease: function(n, event) {
                    console.log(event)
                    this.counter = this.counter - n
                }
            }
        })
    </script>
</body>
</html>

Для передачи объекта события в обработчик передается специальный объект $event.


Двусторонняя привязка

Ранее были рассмотрены примеры использования односторонней привязки с помощью интерполяции. Но кроме того, Vue.js поддерживает и двустороннюю привязку. Для создания подобной привязки используется директива v-model. Например:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <input type="text" v-model="name" /><br>
        <input type="text" v-model="name" />
        <p>Name: {{ name }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                name: 'Tom',
                age: 99
            }
        })
    </script>
</body>
</html>

Здесь на странице определено два текстовых поля, и оба они привязаны к свойству name из объекта Vue. В этом случае нам не надо добавлять к текстовому полю обработчик события ввода, и в этом обработчике менять вручную значение свойства name. При изменении текста в одном из полей автоматически изменится значение в другом. То есть сработает двусторонняя привязка.

Другой пример - определим программу вычисления факториала:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <input type="text" v-model="number" />
        <p>Факториал числа {{ number }} равен {{ factorial(number) }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                number: 1
            },
            methods: {
                factorial: function(n) {
                    let result = 1
                    for (let i=1; i<=n; i++) {
                        result *= i
                    }
                    return result
                }
            }
        })
    </script>
</body>
</html>

При вводе числа в текстовое поле функция factorial автоматически будет перевычислять значение.


Вычисляемые свойства

Кроме обычных свойств объект Vue может содержать вычисляемые свойства, который во многом аналогичны функциям, но в то же время отличаются от них. Рассмотрим небольшой пример:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <input type="text" v-model="name" />
        <input type="text" v-model="age" />
        <p>Name: {{ name }} Age: {{ age }}</p>
        <p>{{ checkAge() }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                name: 'Tom',
                age: 25
            },
            methods: {
                checkAge: function() {
                    console.log('method')
                    if (this.age > 17) {
                        return 'Access allowed'
                    } else {
                        return 'Access denied'
                    }
                }
            }
        })
    </script>
</body>
</html>

Здесь в зависимости от возраста пользователя функция checkAge возвращает некоторый результат. И при каждом изменении значения свойства age, функция checkAge будет пересчитывать свой результат. Однако минусом подобного подхода является то, что метод checkAge будет выполняться при изменении любого свойства во Vue, в том числе свойства name, которое с методом checkAge никак не связано. Это мы можем увидеть по консольному выводу из метода checkAge при изменении свойства name.

Более оптимальным в данном случае будет использование вычисляемых свойств, которые определяются с помощью параметра computed:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <input type="text" v-model="name" />
        <input type="text" v-model="age" />
        <p>Name: {{ name }} Age: {{ age }}</p>
        <p>{{ checkAge() }}</p>
        <p>{{ enabled }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                name: 'Tom',
                age: 25
            },
            computed: {
                enabled: function() {
                    console.log('computed')
                    if (this.age > 17) {
                        return 'Access allowed'
                    } else {
                        return 'Access denied'
                    }
                }
            },
            methods: {
                checkAge: function() {
                    console.log('method')
                    if (this.age > 17) {
                        return 'Access allowed'
                    } else {
                        return 'Access denied'
                    }
                }
            }
        })
    </script>
</body>
</html>

Здесь enabled представляет вычисляемое свойство. Его определение во многом аналогично методу checkAge. Но теперь при изменении состояния Vue будут анализироваться сделанные изменения, и если потребуется, то свойство enabled будет повторно вычисляться. Поэтому в данном случае, если мы изменим свойство age, изменится свойство enabled и повторно выполнится функция checkAge. Но если изменится свойство name, то свойство enabled не будет изменяться.

Сеттеры

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

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <input type="text" v-model="firstname" />
        <input type="text" v-model="lastname" />
        <input type="text" v-model="fullname" />
        <p>Name: {{ fullname }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                firstname: 'Tom',
                lastname: 'Smith'
            },
            computed: {
                fullname: {
                    get: function() {
                        return this.firstname + ' ' + this.lastname
                    },
                    set: function(newValue) {
                        const names = newValue.split(' ')
                        this.firstname = names[0]
                        this.lastname = names[names.length - 1]
                    }
                }
            }
        })
    </script>
</body>
</html>

На уровне кода геттер задается через параметр get, который представляет функцию, возвращающую значение. А сеттер представляет функцию, задаваемую через параметр set. Причем каждое новое значение передается через параметр этой функции (в примере выше параметр newValue). И так как в данном случае вычисляемое свойство представляет объединение простых свойств firstname и lastname, то в сеттере мы можем получить по отдельности значения этих свойств после изменения свойства fullname.


Привязка классов

С помощью привязки атрибута class во Vue.js можно динамически управлять классами элементов. Для условной привязки классов определим следующую страницу:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
    <style>
        .region {
            background-color: #999;
            width: 100px;
            height: 100px;
            display: inline-block;
            margin: 10px;
        }
        .active {
            background-color: red;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="region" v-bind:class="{active: isActive}" v-on:click="isActive = !isActive"></div>
        <div class="region"></div>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                isActive: false
            }
        })
    </script>
</body>
</html>

Здесь определены два блока с классом region. Кроме того, у первого блока применяется условная привязка классов. Она имеет форму: {класс_1: true|false, класс_2: true|false, класс_N: true|false}

Если для класса определено значение true, то класс применяется. В данном случае применение класса зависит от свойства isActive, которое определено в объекте Vue. А с помощью события click у первого блока мы можем переключить значение свойства isActive, и, следовательно, включить или отключить класс active.

При этом не столь важно, определены у элемента какие-либо еще классы, все они вместе будут применяться к элементу.

Подобным образом можно привязать несколько классов:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
    <style>
        .region {
            background-color: #999;
            width: 100px;
            height: 100px;
            display: inline-block;
            margin: 10px;
        }
        .active {
            background-color: red;
        }
        .bounded {
            border: 2px solid green;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="region" v-bind:class="{active: isActive, bounded: isBounded}"></div>
        <div class="region"></div>
        <button v-on:click="isActive = !isActive">Toggle color</button>
        <button v-on:click="isBounded = !isBounded">Toggle border</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                isActive: false,
                isBounded: false
            }
        })
    </script>
</body>
</html>

Если название класса имеет какие-либо неалфавитно-цифровые символы, то оно помещается в кавычки:

v-bind:class="{'active-color': isActive, bounded: isBounded}"

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

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
    <style>
        .region {
            background-color: #999;
            width: 100px;
            height: 100px;
            display: inline-block;
            margin: 10px;
        }
        .active {
            background-color: red;
        }
        .bounded {
            border: 2px solid green;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="region" v-bind:class="classObj"></div>
        <div class="region"></div>
        <button v-on:click="isActive = !isActive">Toggle color</button>
        <button v-on:click="isBounded = !isBounded">Toggle border</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                isActive: false,
                isBounded: false
            },
            computed: {
                classObj: function() {
                    return {
                        active: this.isActive,
                        bounded: this.isBounded
                    }
                }
            }
        })
    </script>
</body>
</html>

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

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
    <style>
        .region {
            background-color: #999;
            width: 100px;
            height: 100px;
            display: inline-block;
            margin: 10px;
        }
        .yellow {
            background-color: yellow;
        }
        .red {
            background-color: red;
        }
        .blue {
            background-color: blue;
        }
        .green {
            background-color: green;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="region" v-bind:class="color"></div>
        <input type="text" v-model="color">
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                color: ''
            }
        })
    </script>
</body>
</html>

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

Если надо установить привязку сразу к нескольким классом, то их можно перечислить в квадратных скобках:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
    <style>
        .region {
            background-color: #999;
            width: 100px;
            height: 100px;
            display: inline-block;
            margin: 10px;
        }
        .bounded {
            border: 2px solid black;
        }
        .yellow {
            background-color: yellow;
        }
        .red {
            background-color: red;
        }
        .blue {
            background-color: blue;
        }
        .green {
            background-color: green;
        }
        .big {
            width: 100px;
            height: 100px;
        }
        .small {
            width: 50px;
            height: 50px;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="region" v-bind:class="[color, {bounded: isBounded}, size]" v-on:click="isBounded = !isBounded"></div><br>
        <input type="text" v-model="color"><br>
        <input type="text" v-model="size">
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                color: 'red',
                size: 'small',
                isBounded: false
            }
        })
    </script>
</body>
</html>

Привязка к стилям

Кроме классов Vue.js позволяет с помощью привязки атрибутов управлять стилями html-элемента. Для привязки к стилям атрибуту style можно передать объект, который содержит стилевые свойства и их значения:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <div class="region" v-bind:style="{'background-color': color, height: height + 'px', width: width + 'px'}"></div><br>
        <input type="text" v-model="color"><br>
        <input type="number" v-model="width"><br>
        <input type="number" v-model="height">
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                color: 'red',
                width: 75,
                height: 75
            }
        })
    </script>
</body>
</html>

Для установки стилей атрибуту style предаются названия свойств CSS и их значения. При этом в качестве значения могут выступать как обычные значения стилевых свойств, так и свойства из объекта Vue, через которые мы можем манипулировать стилями.

Стоит учитывать, что если свойство CSS в своем названии имеет дефисы, то название свойства заключается в кавычки, как в случае со свойством background-color.

Если необходимо устанавливать много различных стилевых свойств, то их можно вынести в отдельное вычисляемое свойство:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <div class="region" v-bind:style="styleObj"></div><br>
        <input type="text" v-model="color"><br>
        <input type="number" v-model="width"><br>
        <input type="number" v-model="height">
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                color: 'red',
                width: 75,
                height: 75
            },
            computed: {
                styleObj: function() {
                    return {
                        'background-color': this.color,
                        height: this.height + 'px',
                        width: this.width + 'px'
                    }
                }
            }
        })
    </script>
</body>
</html>

Фактически каждый такой объект представляет отдельный стиль.

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

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <div class="region" v-bind:style="[colorStyle, sizeStyle]"></div><br>
        <input type="text" v-model="color"><br>
        <input type="number" v-model="width"><br>
        <input type="number" v-model="height">
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                color: 'red',
                width: 75,
                height: 75
            },
            computed: {
                colorStyle: function() {
                    return {
                        'background-color': this.color
                    }
                },
                sizeStyle: function() {
                    return {
                        height: this.height + 'px',
                        width: this.width + 'px'
                    }
                }
            }
        })
    </script>
</body>
</html>

Наблюдаемые свойства

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

Например, определим следующую веб-страницу:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <p>Введите число: <input v-model="number"></p>
        <p>{{ result }}</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                number: '',
                result: ''
            },
            watch: {
                number: function(newNumber) {
                    if (newNumber > 0) {
                        this.factorial(newNumber)
                    }
                }
            },
            methods: {
                factorial: function(newNumber) {
                    this.result = 'Идет вычисление факториала...'
                    
                    let vm = this
                    setTimeout(() => {
                        let res = 1
                        for (let i=1; i<=newNumber; i++) {
                            res = res * i
                        }
                        vm.result = 'Факториал числа ' + newNumber + ' равен ' + res
                    }, 2000);
                }
            }
        })
    </script>
</body>
</html>

Для создания наблюдаемого свойства используется параметр watch. В данном случае определяется наблюдаемое свойство number, которое указывает на функцию. Причем в параметре data также определяется свойство number. При изменении свойства number, которое определено в секции data, будет вызываться функция number, определенная в параметре watch. Измененное значение будет передаваться в функцию через параметр newNumber.

Функция number в зависимости от нового значения свойства number вызывает метод factorial. В этом методе устанавливается значение свойства result. Причем вначале устанавливается некоторое промежуточное значение. Затем для имитации продолжительной операции применяется функция setTimeout(), которая выполняет задержку на 2 секунды, после которой вычисляет факториал числа. И в конце вычисленный результат передается свойству result.

Поэтому при изменении числа в текстовом поле вначале мы увидим временную надпись. И затем собственно результат - факториал числа.


Работа с объектом Vue

Объект Vue является стандартным объектом JavaScript, с которым мы можем работать из остального кода на JS, в том числе и в других объектах Vue. Например, определим следующую страницу:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app1">
        <input type="text" v-model="name">
        <input type="number" v-model="age">
        <p>{{ name }} - {{ age }}</p>
    </div>
    <div id="app2">
        <input type="text" v-model="title">
        <button v-on:click="onClick">Change</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const vm1 = new Vue({
            el: '#app1',
            data: {
                name: 'Tom',
                age: 22
            },
            methods: {
                setName: function(value) {
                    this.name = value
                }
            }
        })
        // установка свойств объекта Vue в обычном коде JS
        vm1.name = 'Sam'
        vm1.setName('Mike')

        // установка свойств объекта Vue в другом объекте Vue
        const vm2 = new Vue({
            el: '#app2',
            data: {
                title: ''
            },
            methods: {
                onClick: function() {
                    vm1.setName(this.title)
                    // vm1.name = this.title  // или так
                }
            }
        })
    </script>
</body>
</html>

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

vm1.name = 'Sam'
vm1.setName('Mike')

В данном случае по нажатию на кнопку во втором объекте Vue мы можем изменить значения в первом объекте Vue.


refs и управление html-элементами

С помощью параметра refs можно ссылаться на определенный html-элемент из шаблона и управлять им. Например, определим следующую страницу:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <h2 ref="header">Hello, World!</h2>
        <button v-on:click="change">Change</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const vm1 = new Vue({
            el: '#app',
            methods: {
                change: function() {
                    this.$refs.header.innerText = 'Welcome!'
                }
            }
        })
    </script>
</body>
</html>

С помощью атрибута ref для html-элемента устанавливается ключ, через который потом можно ссылаться на этот элемент:

<h2 ref="header">Hello, World!</h2>

В методах объекта Vue можно обратиться к элементу по установленному ключу через объект $refs:

this.$refs.header.innerText = 'Welcome!'

С объектом, который представляет html-элемент, (в данном случае this.$refs.header) фактически можно также работать, как и со стандартныи объектами JavaScript, которые представляют html-элементы. То есть мы можем обратиться к его свойствам innerText или innerHTML. Хотя в реальности объект this.$refs.header будет представлять надстройку над объектом JS, который собственно и представляет html-элемент. Но фактически через this.$refs.header мы сможем уравлять элементом h2 на веб-странице.


Жизненный цикл Vue

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

Весь жизненный цикл приложения Vue.js можно описать следующим образом:

1. Вызывается конструктор new Vue()

2. Перед созданием объекта Vue вызывается метод beforeCreate()

3. Далее происходит инициализация объекта Vue, установка его данных и методов

4. После создания объекта Vue вызывается метод created()

5. Далее выполняется компиляция шаблона

6. Вызывается метод beforeMount()

7. Элемент HTML, к которому прикреплен объект Vue, заменяется скомпилированным шаблоном

8. Вызывается метод mounted(), и после этого шаблон прикреплен к DOM, и мы можем с ним работать

9. Если в процессе работы мы обновляем данные объекта Vue, то происходит еще ряд событий:

  1. Данные изменяются
  2. Вызывается метод beforeUpdate()
  3. Производится повторный рендеринг DOM для его соответствия виртуальному DOM
  4. Вызывается метод updated(). DOM на веб-странице обновлен, и мы продолжаем работать с приложением Vue.js

10. При завершении работы приложения вызывается метод beforeDestroy()

11. И в конце вызывается метод destroyed(). Объект Vue удалено из памяти, и больше с ним мы работать не можем

Схематично жизненный цикл можно представить так:

Для примера используем методы управления жизненным циклом:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <h2>{{ message }}</h2>
        <button v-on:click="message = 'Updated'">Update</button>
        <button v-on:click="destroy">Destroy</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                message: 'Hello, Vue!'
            },
            methods: {
                destroy: function() {
                    this.$destroy()
                }
            },
            beforeCreate: function() {
                console.log('beforeCreate()');
            },
            created: function() {
                console.log('created()');
            },
            beforeMount: function() {
                console.log('beforeMount()');
            },
            mounted: function() {
                console.log('mounted()');
            },
            beforeUpdate: function() {
                console.log('beforeUpdate()');
            },
            updated: function() {
                console.log('updated()');
            },
            beforeDestroy: function() {
                console.log('beforeDestroy()');
            },
            destroyed: function() {
                console.log('destroyed()');
            }
        })
    </script>
</body>
</html>

При нажатии на кнопку Update выполняется обновление DOM, и вызываются методы beforeUpdate() и updated(). Причем при повторном нажатии эти методы не будут в данном случае вызываться, так как виртуальный DOM никак не изменится, то есть заголовок получит тот же самый текст Update. Соответственно реальный DOM также не будет обновляться.

При нажатии на кнопку Destroy будет вызван метод destroy, в котором будет выполняться метод this.$destroy(). То есть мы явным образом вызываем у объекта Vue его уничтожение. Затем вызываются методы beforeDestroy() и destroyed(). И после этого объект Vue удален из памяти, и работать с ним мы больше не сможем, даже при том, что на веб-странице сохраняется его html-разметка.