← Курс/v-bind: динамические атрибуты#206 из 257+20 XP

v-bind: динамические атрибуты

Что такое v-bind

Директива v-bind позволяет **динамически привязывать значения к HTML-атрибутам**. Без неё атрибуты всегда статические — значение задаётся прямо в шаблоне и не меняется. С v-bind значение атрибута берётся из переменной компонента и автоматически обновляется при её изменении.

<!-- Статический атрибут — всегда одинаковый -->
<a href="https://example.com">Ссылка</a>

<!-- Динамический атрибут — значение из переменной -->
<a v-bind:href="url">Ссылка</a>

Сокращённый синтаксис

Директива v-bind используется настолько часто, что Vue предоставляет сокращение — двоеточие ::

<!-- Полная запись -->
<img v-bind:src="imageUrl" v-bind:alt="imageAlt">

<!-- Сокращённая запись (рекомендуется) -->
<img :src="imageUrl" :alt="imageAlt">

<script setup>
const imageUrl = ref('https://example.com/photo.jpg')
const imageAlt = ref('Фото пользователя')
</script>

Привязка к объекту

Если нужно привязать сразу несколько атрибутов, можно передать объект в v-bind без указания конкретного атрибута:

<template>
  <input v-bind="inputAttrs">
</template>

<script setup>
const inputAttrs = reactive({
  type: 'text',
  placeholder: 'Введите текст',
  maxlength: 100,
  disabled: false,
})
</script>

Это эквивалентно записи :type="inputAttrs.type" :placeholder="inputAttrs.placeholder" и т.д.

Динамические имена классов

Привязка :class — одна из самых мощных возможностей v-bind. Можно передавать объект, где ключи — имена классов, а значения — условия их применения:

<template>
  <button
    :class="{
      btn: true,
      'btn-primary': isPrimary,
      'btn-disabled': isDisabled,
      active: isActive
    }"
  >
    Кнопка
  </button>
</template>

Класс добавляется, только если значение в объекте равно true.

Привязка стилей

Директива :style принимает объект CSS-свойств. Имена свойств можно писать в camelCase или в кавычках в kebab-case:

<template>
  <div :style="{
    color: textColor,
    fontSize: fontSize + 'px',
    'background-color': bgColor,
    fontWeight: isBold ? 'bold' : 'normal'
  }">
    Стилизованный блок
  </div>
</template>

<script setup>
const textColor = ref('#333')
const fontSize = ref(16)
const bgColor = ref('#f5f5f5')
const isBold = ref(false)
</script>

Динамические имена атрибутов

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

<!-- attrName может быть 'href', 'src', 'disabled' и т.д. -->
<component :[attrName]="attrValue" />

Практический пример

<template>
  <a
    :href="link.url"
    :target="link.external ? '_blank' : '_self'"
    :rel="link.external ? 'noopener noreferrer' : null"
  >
    {{ link.text }}
  </a>
</template>

<script setup>
const link = reactive({
  url: 'https://vuejs.org',
  text: 'Документация Vue',
  external: true,
})
</script>

Обратите внимание: если атрибут равен null или undefined, Vue полностью убирает его из DOM-элемента.

Примеры

Эмуляция v-bind: динамическая установка атрибутов DOM-элемента через объект с настройками

// Эмулируем поведение v-bind — динамическое применение атрибутов к элементу.
// В Vue это делается декларативно в шаблоне, здесь показана логика "под капотом".

// Функция применяет объект атрибутов к DOM-элементу (или псевдо-элементу)
function applyBindings(element, bindings) {
  for (const [attr, value] of Object.entries(bindings)) {
    if (value === null || value === undefined) {
      // v-bind удаляет атрибут если значение null/undefined
      console.log(`  Атрибут "${attr}" удалён (значение null/undefined)`)
      delete element[attr]
    } else {
      element[attr] = value
      console.log(`  Атрибут "${attr}" = ${JSON.stringify(value)}`)
    }
  }
}

// Псевдо-DOM-элемент (в браузере это был бы document.createElement)
const pseudoElement = {}

// Начальное состояние — как v-bind на первом рендере
console.log('=== Начальный рендер ===')
applyBindings(pseudoElement, {
  href: 'https://vuejs.org',
  target: '_blank',
  rel: 'noopener noreferrer',
  disabled: null,  // null — атрибут не добавляется
})

console.log('\nСостояние элемента:', pseudoElement)

// Обновление состояния — как v-bind при изменении реактивных данных
console.log('\n=== После обновления данных ===')
applyBindings(pseudoElement, {
  href: 'https://vuejs.org/guide/',
  target: '_self',
  rel: null,       // убираем rel
  disabled: true,  // теперь кнопка отключена
})

console.log('\nОбновлённое состояние:', pseudoElement)

// Эмуляция :class с объектом
console.log('\n=== Динамические классы (:class) ===')
function resolveClass(classObject) {
  return Object.entries(classObject)
    .filter(([, active]) => active)
    .map(([name]) => name)
    .join(' ')
}

const isPrimary = true
const isDisabled = false
const isActive = true

const className = resolveClass({
  btn: true,
  'btn-primary': isPrimary,
  'btn-disabled': isDisabled,
  active: isActive,
})

console.log('Итоговый класс:', className)
// btn btn-primary active

// Эмуляция :style с объектом
console.log('\n=== Динамические стили (:style) ===')
function resolveStyle(styleObject) {
  return Object.entries(styleObject)
    .map(([prop, val]) => `${prop}: ${val}`)
    .join('; ')
}

const fontSize = 16
const textColor = '#333'
const isBold = true

const styleAttr = resolveStyle({
  'font-size': fontSize + 'px',
  color: textColor,
  'font-weight': isBold ? 'bold' : 'normal',
})

console.log('Итоговый style:', styleAttr)