Рендеринг списков и Vue директива v-for

Рендеринг списков и Vue директива v-for

От автора: рендеринг списков – одна из наиболее используемых практик в front end разработке. Динамический рендеринг списков часто используется для представления серии схожей сгруппированной информации в сжатом и дружелюбном для пользователя формате. Почти в каждом используемом нам веб-приложении можно найти списки контента во множестве областей. В этой статье я расскажу про директиву Vue v for при создании динамических списков, покажу пару примеров о том, почему для этого нужно использовать атрибут key. Мы все будем подробно объяснять при написании кода. Статья предполагает, что у вас нет знаний по Vue, или вы знаете совсем немного.

Пример: Twitter

В качестве примера для статьи возьмем Twitter. После авторизации на главном роуте index в Twitter нам открывается следующий вид:

Рендеринг списков и Vue директива v-for

На домашней странице мы привыкли видеть список трендов, список твитов, список возможных фоловеров и т.д. Контент в этих списках зависит от множества факторов – наши история в Twitter, наши подписки, наши лайки и т.д. В общем, можно сказать, все эти данные динамические.

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

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

Рендеринг списков и Vue директива v-for

Скажем, нам нужно отрендерить список компонентов (например, список элементов tweet-component) на основе большого объема данных, полученных с сервера. Первое, что должно прийти на ум в Vue, это директива v-for.

Директива v-for

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

Рендеринг списков и Vue директива v-for

Разберем пример. Предполагаем, что уже получили коллекцию твитов:

const tweets = [ { id: 1, name: 'James', handle: '@jokerjames', img: 'https://semantic-ui.com/images/avatar2/large/matthew.png', tweet: "If you don't succeed, dust yourself off and try again.", likes: 10, }, { id: 2, name: 'Fatima', handle: '@fantasticfatima', img: 'https://semantic-ui.com/images/avatar2/large/molly.png', tweet: 'Better late than never but never late is better.', likes: 12, }, { id: 3, name: 'Xin', handle: '@xeroxin', img: 'https://semantic-ui.com/images/avatar2/large/elyse.png', tweet: 'Beauty in the struggle, ugliness in the success.', likes: 18, }
]

Tweets – это коллекция объектов tweet. В каждом tweet содержится детальная информация одного твита – уникальный id, имя, id профиля, сообщение и т.д. Теперь давайте попробуем с помощью директивы v-for отрендерить список компонентов tweet на основе этих данных.

Прежде всего создаем объект Vue – сердце приложения Vue. Мы подключим/прицепим наш объект к DOM элементу с id app и присвоим коллекцию tweets, как часть объекта данных объекта Vue.

new Vue({ el: '#app', data: { tweets }
});

Теперь создадим tweet-component, который будет использовать директива v-for для рендера списка. Используем глобальный конструктор Vue.component для создания компонента tweet-component:

Vue.component('tweet-component', { template: ` <div class="tweet"> <div class="box"> <article class="media"> <div class="media-left"> <figure class="image is-64x64"> <img :src="tweet.img" alt="Image"> </figure> </div> <div class="media-content"> <div class="content"> <p> <strong>{{tweet.name}}</strong> <small>{{tweet.handle}}</small> <br> {{tweet.tweet}} </p> </div> <div class="level-left"> <a class="level-item"> <span class="icon is-small"><i class="fas fa-heart"></i></span> <span class="likes">{{tweet.likes}}</span> </a> </div> </div> </article> </div> </div> `, props: { tweet: Object }
});

Что здесь стоит отметить:

tweet-component ожидает prop объект tweet, как видно из требования валидации prop (props: {tweet: Object}). Если компонент рендерится с tweet prop, который не является объектом, Vue пропустит предупреждения.

Мы привязываем свойства prop объекта tweet к шаблону компонента с помощью синтаксиса {{ }}.

Разметка компонента адаптирует элемент Bulma Box, так как он сильно похож на твит.

В HTML шаблоне нужно создать разметку, куда будет подключаться наше Vue приложение (например, элемент с id app). Внутри этой разметку мы будем использовать v-for для рендера списка твитов. Tweets – это коллекция данных, по которой мы будем бегать в цикле. Поэтому подходящим алиасом для использования в директиве будет tweet. В каждый отрендереный tweet-component будем передавать объект tweet из цикла в виде props, чтобы он был доступен в компоненте.

<div id="app" class="columns"> <div class="column"> <tweet-component v-for="tweet in tweets" :tweet="tweet"/> </div>
</div>

Независимо от количества объектов tweet в коллекции и того, как они могут меняться со временем, наш код всегда будет рендерить все твиты из коллекции в той же разметке. Добавим немного своего CSS, и наше приложение выглядит примерно так:

Все работает, как надо, но в консоли можно обнаружить совет от Vue:

[Vue tip]: <tweet-component v-for="tweet in tweets">: component lists rendered with v-for should have explicit keys...

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

Почему Vue говорит нам указать явные ключи списка, когда все работает?

Key

Для элементов в цикле принято добавлять атрибут key внутри списка v-for. Vue использует атрибут key для создания уникальной привязки для каждой идентичности узла.

Давайте разъясним – если в наш список будут внесены UI изменения (например, перемешается порядок элементов), Vue будет менять данные в каждом элементе, а не двигать DOM элементы. В большинстве случаев это не будет проблемой. Тем не менее, в определенных сценариях, где список v-for зависит от состояния DOM и/или состояния дочернего компонента, это может привести к нежелательному поведению.

Разберем пример. Что, если бы наш простой компонент твита содержал поле ввода, позволяющее пользователю автоматически отвечать на твит? Не будем вдаваться в то, как этот ответ будет отправлен, просто обратимся к новому полю ввода:

Рендеринг списков и Vue директива v-for

Добавим новое поле ввода в шаблон tweet-component:

Vue.component('tweet-component', { template: ` <div class="tweet"> <div class="box"> // ... </div> <div class="control has-icons-left has-icons-right"> <input class="input is-small" placeholder="Tweet your reply..." /> <span class="icon is-small is-left"> <i class="fas fa-envelope"></i> </span> </div> </div> `, props: { tweet: Object }
});

Предположим, что хотели добавить новую функцию в приложение. Эта функция позволяет перемешивать случайным образом список твитов. Для этого сначала нужно подключить кнопку Shuffle! в HTML шаблоне:

<div id="app" class="columns"> <div class="column"> <button class="is-primary button" @click="shuffle">Shuffle!</button> <tweet-component v-for="tweet in tweets" :tweet="tweet"/> </div>
</div>

Мы подключили обработчик события click на кнопку. По клику вызывается метод shuffle. В объекте Vue создадим метод shuffle, который будет случайно перемешивать коллекцию tweets в объекте. Для этого используем метод lodash _shuffle:

new Vue({ el: '#app', data: { tweets }, methods: { shuffle() { this.tweets = _.shuffle(this.tweets) } }
});

Давайте проверим! Если кликнуть несколько раз на shuffle, мы заметим, что каждый клик случайным образом перемешивает твиты.

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

Рендеринг списков и Vue директива v-for

Мы не стали использовать атрибут key, поэтому Vue не создал уникальные привязки к узлам твитов. Как результат, когда мы хотим отсортировать твиты, Vue идет самым быстрым способом и просто меняет данные во всех элементах. Временное состояние DOM (например, введенный текст) не затрагивается, поэтому нам это не подходит.

На рисунке показано, как во всех элементах меняются данные, а состояние DOM нет:

Рендеринг списков и Vue директива v-for

Чтобы решить эту проблему, нам нужно присвоить уникальный key всем tweet-component, рендерящимся в списке. В качестве уникального идентификатора возьмем id tweet, так как мы точно знаем, что они все разные. Мы используем динамические значения, поэтому используем директиву v-bind для привязки нашего key к tweet.id:

<div id="app" class="columns"> <div class="column"> <button class="is-primary button" @click="shuffle">Shuffle!</button> <tweet-component v-for="tweet in tweets" :tweet="tweet" :key="tweet.id" /> </div>
</div>

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

Рендеринг списков и Vue директива v-for

Теперь твиты перемещаются правильно. Можно показать, как именно сортируются элементы с помощью Vue transition-group.

Для этого добавим элемент transition-group как обертку к списку v-for. Укажем имя перехода tweets и объявим, что группа перехода должна рендериться как тег div.

<div id="app" class="columns"> <div class="column"> <button class="is-primary button" @click="shuffle">Shuffle!</button> <transition-group name="tweets" tag="div"> <tweet-component v-for="tweet in tweets" :tweet="tweet" :key="tweet.id" /> </transition-group> </div>
</div>

По имени перехода Vue автоматически распознает любые установленные CSS переходы/анимации. Наша цель – переместить элементы в списке с помощью перехода. Поэтому Vue будет искать CSS переход в строках tweets-move (где tweets – имя, указанное в transition group). Как результат, мы вручную вводим класс .tweets-move, в котором задан определенный тип и время перехода:

#app .tweets-move { transition: transform 1s;
}

Это очень краткий пример применения переходов к спискам. Посмотреть все типы переходов можно в документации Vue.

Теперь наши элементы tweet-component будут плавно менять положение при перемешивании. Попробуйте! Введите что-нибудь в поля ввода и кликните Shuffle! несколько раз.

Круто, правда? Без атрибута key мы бы не смогли использовать элемент transition-group для создания перехода в списке, так как вместо элементов менялись бы данные в них.

Стоит ли всегда использовать атрибут key? Рекомендуется. Спецификация Vue утверждает, что атрибут key не стоит использовать только:

Нам специально нужна стандартная замена элементов для повышения производительности

Контента DOM просто достаточно

Заключение

Вот и все! Надеюсь, эта короткая статья показала полезность директивы v-for и объяснила, почему атрибут key так часто используется. Если у вас возникли вопросы/мысли, дайте мне знать.

Автор: Hassan Djirdeh

Источник: https://css-tricks.com/

Редакция: Команда webformyself.