v-if

Ряд директив позволяет по условию изменять структуру DOM, и одной из таких является директива v-if. Она позволяет отобразить или скрыть элемент HTML по условию. Например:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <p v-if="visible">Первый параграф</p>
        <p>Второй параграф</p>
        <button v-on:click="visible = !visible">{{ visible ? 'Скрыть' : 'Показать' }}</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                visible: true
            }
        })
    </script>
</body>
</html>

Директива v-if в качестве параметра принимает условие, которое возвращает значение true или false. Если true, то элемент, к которому применяется директива v-if, отображается. Если false, то, наоборот, скрывается. В данном примере это значение определено в свойстве visible. С помощью кнопки в примере выше мы можем изменить значение свойства visible и соответственно отобразить или скрыть элемент.

В паре с директивой v-if может использоваться директива v-else, которая позволяет отобразить другой элемент, если условие в директиве v-if равно false:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <p v-if="visible">Первый параграф</p>
        <p v-else>Второй параграф</p>
        <button v-on:click="visible = !visible">{{ visible ? 'К параграфу 2' : 'К параграфу 1' }}</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                visible: true
            }
        })
    </script>
</body>
</html>

В данном случае, если условие, которое представляет свойство visible, равно true, то отображается первый параграф. Если условие возвращает false, то отображается второй параграф.

template

Так как выражение v-if является директивой, то оно может применяться только к одному элементу. Но что если мы хотим применить ее к группе элементов? В этом случае мы можем применить v-if для элемента <template>, который выступает в качестве обертки для группы элементов:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <template v-if="visible">
            <h1>Заголовок 1</h1>
            <p>Параграф 1</p>
        </template>
        <template v-else>
            <h1>Заголовок 2</h1>
            <p>Параграф 2</p>
        </template>
        <button v-on:click="visible = !visible">{{ visible ? 'К параграфу 2' : 'К параграфу 1' }}</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                visible: true
            }
        })
    </script>
</body>
</html>

v-else-if

С помощью директивы v-else-if к v-if можно добавить обработку дополнительных условий. Например, в зависимости от введенного числа необходимо отображать тот или иной элемент:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <input type="number" v-model="number" />
        <p v-if="number == 1">Один</p>
        <p v-else-if="number == 2">Два</p>
        <p v-else-if="number > 2 && number < 7">Несколько</p>
        <p v-else>Много</p>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                number: 1
            }
        })
    </script>
</body>
</html>

Аналогично можно использовать v-else-if вместе с template:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <input type="number" v-model="number" />
        <template v-if="number == 1">
            <p>Один</p>
        </template>
        <template v-else-if="number == 2">
            <p>Два</p>
        </template>
        <template v-else-if="number > 2 && number < 7">
            <p>Несколько</p>
        </template>
        <template v-else>
            <p>Много</p>
        </template>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                number: 1
            }
        })
    </script>
</body>
</html>

v-show

Директива v-show аналогично v-if позволяет скрывать или отображать элементы по определенному условию:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <div v-show="visible">
            <h2>Заголовок</h2>
            <p>Текст</p>
        </div>
        <button v-on:click="visible = !visible">{{ visible ? 'Скрыть' : 'Отобразить' }}</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                visible: true
            }
        })
    </script>
</body>
</html>

Здесь в зависимости от значения условия, которое представляет свойство visible, будет скрываться или отображаться блок div. А с помощью кнопки мы можем переключить значение этого свойства с true на false и наоборот.

Но в отличие от v-if директива v-show не изменяет структуру DOM, а манипулирует значением стилевого свойства display. То есть если условие в v-show возвращает false, то для элемента устанавливается стиль display:none; и тем самым данный элемент скрывается на веб-странице.

В то же время манипуляции с DOM через v-if увеличивают накладные расходы и снижают производительность. Поэтому в тех ситуациях, когда возможно частое переключение видимости элемента, следует предпочитать v-show.


v-for

Для рендеринга коллекций предназначена директива v-for. Она имеет следующий синтаксис:

v-for="item in items"

Где items представляет массив, а item псевдоним для текущего перебираемого элемента из массива items.

Например, выведем массив элементов на страницу:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="phone in phones">{{ phone }}</li>
        </ul>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                phones: ['iPhone', 'Samsung', 'Nokia', 'Xiaomi']
            }
        })
    </script>
</body>
</html>

Таким образом, для каждого элемента в массиве phones будет создаваться html-элемент <li>.

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

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="user in users">
                <p>Имя: {{ user.name }}</p>
                <p>Возраст: {{ user.age }}</p>
            </li>
        </ul>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                users: [
                    { name: 'Tom', age: 22 },
                    { name: 'Bob', age: 31 },
                    { name: 'Sam', age: 28 }
                ]
            }
        })
    </script>
</body>
</html>

Индексы

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

v-for="(element, index) in array"

Где element - это текущий перебираемый элемент в массиве array, а index - индекс этого элемента в массиве. Например:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="(phone, index) in phones">
                {{ index + 1 }}. {{ phone }}
            </li>
        </ul>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                phones: ['iPhone', 'Samsung', 'Nokia', 'Xiaomi']
            }
        })
    </script>
</body>
</html>

Перебор объектов

Подобно тому, как мы перебираем массив, мы можем перебирать и все свойства одного объекта с помощью синтаксиса:

v-for="(value, property) in obj"

Где property - название свойства объекта, а value - его значение.

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="user in users">
                <p v-for="(value, key) in user">
                    {{ key }} : {{ value }}
                </p>
            </li>
        </ul>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                users: [
                    { name: 'Tom', age: 22 },
                    { name: 'Bob', age: 31 },
                    { name: 'Sam', age: 28 }
                ]
            }
        })
    </script>
</body>
</html>

template

Директиву v-for можно применить только к одному html-элементу. Если необходимо, чтобы для каждого объекта из массива создавалось несколько html-элементов, то блок этих элементов следует обертывать элементом template:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <ul>
            <template v-for="user in users">
                <li>Name: {{ user.name }}</li>
                <li>Age: {{ user.age }}</li>
            </template>
        </ul>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                users: [
                    { name: 'Tom', age: 22 },
                    { name: 'Bob', age: 31 },
                    { name: 'Sam', age: 28 }
                ]
            }
        })
    </script>
</body>
</html>

Перебор чисел

С помощью v-for можно перебрать все числа от 1 до определенного значения. Например, перебор всех чисел от 1 до 10:

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

Управление массивами

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

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

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

Но кроме выше описанных методов, которые изменяют отдельные элементы массива, есть ряд методов в JavaScript, которые возвращают новый массив, типа filter(), concat(), slice(). Результат таких функций лучше привязывать к вычисляемому свойству, которое позволит произвести повторный рендеринг элементов веб-страницы.

Добавление и удаление

Определим код для добавления нового элемента в массив и удаления из массива:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <p>
            <input type="text" v-model="newPhone" />
            <button v-on:click="phones.push(newPhone)">Добавить</button>
        </p>
        <ul>
            <li v-for="(phone, index) in phones">
                <p>{{ phone }} <button v-on:click="phones.splice(index, 1)">Удалить</button></p>
            </li>
        </ul>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                newPhone: '',
                phones: ['iPhone', 'Samsung', 'Nokia', 'Xiaomi']
            }
        })
    </script>
</body>
</html>

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

И кроме того, для каждого элемента предусмотрена кнопка, через которую по индексу можно удалить элемент из массива.

Установка элемента в массиве

При работе с массивами мы можем столкнуться с некоторыми ограничениями. В частности, мы не можем просто по индексу переустановить элемент массива, присвоив ему новое значение:

this.phones[1] = 'Samsung Galaxy S8'

Для установки значения нам надо использовать метод Vue.set():

Vue.set(массив, индекс_элемента, новое_значение)

Например, обновим второй элемент массива:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="phone in phones">
                <p>{{ phone }}</p>
            </li>
        </ul>
        <button v-on:click="updateList">Обновить</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                phones: ['iPhone', 'Samsung', 'Nokia', 'Xiaomi']
            },
            methods: {
                updateList: function() {
                    Vue.set(this.phones, 1, 'Samsung Galaxy S8')
                }
            }
        })
    </script>
</body>
</html>

Возвращение нового массива

Ряд методов, такие как slice, concat, filter не изменяют текущий массив, а возвращают новый. Одно из решений может заключаться в переустановке массива. Например, используем метод slice, который возвращает часть массива:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="phone in phones">
                <p>{{ phone }}</p>
            </li>
        </ul>
        <button v-on:click="updateList">Обновить</button>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                phones: ['iPhone', 'Samsung', 'Nokia', 'Xiaomi']
            },
            methods: {
                updateList: function() {
                    this.phones = this.phones.slice(1, 3)
                }
            }
        })
    </script>
</body>
</html>

Для фиксации нового массива присваиваем полученный массив свойству phones.

Однако данный способ не всегда является оптимальным. Особенно если мы хотим сохранить старый массив и изменять лишь его визуальное представление. И более идеальным вариантом, как правило, является разделение данных и представления этих данных. Для представления данных обычно определяется вычисляемое свойство-список, элементы которого выводятся на веб-страницу:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <input v-model="start" type="number" />
        <input v-model="end" type="number" />
        <ul>
            <li v-for="phone in visibleList">
                <p>{{ phone }}</p>
            </li>
        </ul>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                start: 0,
                end: 3,
                phones: ['iPhone', 'Samsung', 'Nokia', 'Xiaomi']
            },
            computed: {
                visibleList: function() {
                    return this.phones.slice(this.start, this.end);
                }
            }
        })
    </script>
</body>
</html>

Фильтрация и сортировка массива

Фильтрация массива

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

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
</head>
<body>
    <div id="app">
        <p>
            <input type="text" v-model="company" />
        </p>
        <ul>
            <li v-for="phone in filteredList">
                <p>{{ phone.title }} - {{ phone.company }}</p>
            </li>
        </ul>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                company: '',
                phones: [
                    { title: 'iPhone 7', company: 'Apple' },
                    { title: 'iPhone 6S', company: 'Apple' },
                    { title: 'Galaxy S8', company: 'Samsung' },
                    { title: 'Galaxy S7 Edge', company: 'Samsung' },
                    { title: 'Nokia N8', company: 'HMD Global' }
                ]
            },
            computed: {
                filteredList: function() {
                    const company = this.company
                    
                    return this.phones.filter(function (element) {
                        if (company === '') {
                            return true
                        }
                        return element.company.indexOf(company) > -1
                    })
                }
            }
        })
    </script>
</body>
</html>

В данном случае фильтрация работает по принципу живого поиска. При вводе значения в текстовое поле происходит повторное вычисление свойства filteredList. Это свойство представляет результат функции, которая возвращает те объекты, у которых поле company соответствует введенному значению. То есть идет фильтрация телефонов по компании производителя. Если же значение не введено, то возвращаем все элементы из массива phones. В итоге можем динамически фильтровать элементы списка.

Сортировка списка

Для сортировки списка применяется похожая техника, что и для фильтрации:

<!DOCTYPE html>
<html>
<head>
    <title>Vue.js</title>
    <meta charset="utf-8" />
    <style>
        a:hover {
            cursor: pointer;
        }
    </style>
</head>
<body>
    <div id="app">
        <table>
            <tr>
                <td><a @click="sortParam = 'title'">Модель</a></td>
                <td><a @click="sortParam = 'company'">Компания</a></td>
                <td><a @click="sortParam = 'price'">Цена</a></td>
            </tr>
            <tr v-for="phone in sortedList">
                <td>{{ phone.title }}</td>
                <td>{{ phone.company }}</td>
                <td>{{ phone.price }}</td>
            </tr>
        </table>
    </div>
    <script src="https://unpkg.com/vue"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                sortParam: '',
                phones: [
                    { title: 'Galaxy S8', company: 'Samsung', price: 45000 },
                    { title: 'iPhone 7', company: 'Apple', price: 49000 },
                    { title: 'Nokia N8', company: 'HMD Global', price: 25000 },
                    { title: 'Galaxy Note 8', company: 'Samsung', price: 50000 },
                    { title: 'iPhone 8', company: 'Apple', price: 60000 }
                ]
            },
            computed: {
                sortedList () {
                    switch (this.sortParam) {
                        case 'title': return this.phones.sort(sortByTitle)
                        case 'company': return this.phones.sort(sortByCompany)
                        case 'price': return this.phones.sort(sortByPrice)
                        default: return this.phones
                    }
                }
            }
        })

        const sortByTitle = function (d1, d2) {
            return (d1.title.toLowerCase() > d2.title.toLowerCase()) ? 1 : -1
        }
        const sortByCompany = function (d1, d2) {
            return (d1.company.toLowerCase() > d2.company.toLowerCase()) ? 1 : -1
        }
        const sortByPrice = function (d1, d2) {
            return (d1.price > d2.price) ? 1 : -1
        }
    </script>
</body>
</html>

Как и в случае с фильтрацией, привязка устанавливается к вычисляемому свойству. При нажатии на заголовок столбца в таблице происходит переустановка значения свойства sortParam, которое представляет критерий фильтрации. При его изменении повторно вычисляется свойство sortedList, которое сортирует массив phones в соответствии со значением в sortParam. Для фильтрации по трем критериям определены три вспомогательные функции sortByTitle, sortByCompany и sortByPrice.