Олег Марков
Директива v-text в Vue - полное практическое руководство
Введение
Директива v-text в Vue используется для вывода текстового содержимого в шаблоне. По сути, она делает то же самое, что и интерполяция вида {{ message }}, но в более явном и управляемом виде.
Смотрите, здесь важно понять базовую идею. В большинстве случаев вы можете написать:
<span>{{ message }}</span>
и этого будет достаточно. Но иногда требуется явная директива, которая:
- всегда затирает все содержимое элемента;
- гарантированно работает только как текст, без HTML-разметки;
- удобна там, где интерполяция {{ }} по каким-то причинам неудобна или невозможна.
Давайте разберемся, как работает v-text, когда ее использовать, а когда лучше обойтись интерполяциями или другими директивами.
Что делает директива v-text
Основное назначение
Директива v-text связывает текстовое содержимое HTML-элемента со свойством из данных компонента Vue.
Простейший пример:
<div id="app">
<!-- Выводим текст с помощью v-text -->
<p v-text="message"></p>
</div>
<script>
const app = Vue.createApp({
data() {
return {
message: 'Привет, мир' // Это значение попадет в текст параграфа
}
}
})
app.mount('#app')
</script>
Как это работает:
- Вы объявляете поле message в data.
- Vue следит за этим полем.
- v-text берет текущее значение message и устанавливает его как
textContentузла<p>. - При изменении message текст внутри
<p>автоматически обновляется.
Важно: v-text устанавливает именно текст, а не HTML. То есть любая строка интерпретируется как текст, а не как разметка.
Сравнение v-text и интерполяций {{ }}
Обычно вы можете написать тот же код проще:
<p>{{ message }}</p>
По сути, это эквивалентно примеру с v-text, за одним важным исключением: интерполяция обрабатывается как шаблонный синтаксис, а v-text — как директива, которая напрямую меняет текстовое содержимое DOM-узла.
Если говорить ближе к реализации:
{{ message }}внутри шаблона превращается в выражение, которое Vue подставляет в виртуальный DOM;v-text="message"компилируется в инструкцию: взять значение выражения и присвоить егоel.textContent.
С практической стороны — и то, и другое выведет текст. Но поведение в некоторых ситуациях отличается, и мы их сейчас разберем.
Синтаксис и базовое использование
Базовый синтаксис v-text
Запись директивы:
<p v-text="expression"></p>
Где expression — любое корректное выражение JavaScript, доступное в контексте шаблона (data, computed, методы, параметры цикла и т.д.).
Примеры:
<p v-text="message"></p> <!-- Привязка к полю из data -->
<p v-text="user.name"></p> <!-- Вывод свойства вложенного объекта -->
<p v-text="items.length"></p> <!-- Результат выражения -->
<p v-text="message.toUpperCase()"></p> <!-- Вызов метода строки -->
<p v-text="computedFullName"></p> <!-- Привязка к вычисляемому свойству -->
Теперь давайте посмотрим на минимальный рабочий пример с несколькими полями.
<div id="app">
<!-- Простая строка -->
<h2 v-text="title"></h2>
<!-- Выводим длину строки -->
<p v-text="title.length"></p>
<!-- Берем свойство объекта -->
<p v-text="user.email"></p>
<!-- Используем вычисляемое свойство -->
<p v-text="fullInfo"></p>
</div>
<script>
const app = Vue.createApp({
data() {
return {
title: 'Профиль пользователя', // Заголовок
user: {
name: 'Анна', // Имя
email: 'anna@example.com' // Почта
}
}
},
computed: {
fullInfo() {
// Собираем строку на основе полей объекта
return `${this.user.name} - ${this.user.email}`
}
}
})
app.mount('#app')
</script>
Комментарии в коде помогают увидеть, откуда именно берутся значения для v-text.
Двусторонняя ли это привязка
Давайте уточним важный момент: v-text обеспечивает только вывод (one-way binding, однонаправленная привязка от данных к DOM). Из DOM обратно в данные она не пишет.
Если вам нужна двусторонняя привязка (например, с полями ввода), используется другая директива — v-model. v-text же просто отображает текущее значение.
Как ведет себя v-text относительно содержимого элемента
v-text затирает всё внутреннее содержимое
Ключевая особенность: v-text всегда полностью перезаписывает содержимое элемента. Внутри тега не имеет значения, что вы напишете — это будет удалено и заменено текстом, рассчитанным из выражения.
Давайте посмотрим:
<div id="app">
<p v-text="message">
Этот текст никогда не будет показан <!-- Это содержимое исчезнет -->
</p>
</div>
<script>
const app = Vue.createApp({
data() {
return {
message: 'Реальный текст параграфа' // Это и будет показано
}
}
})
app.mount('#app')
</script>
После монтирования приложения содержимое параграфа станет:
<p>Реальный текст параграфа</p>
Вложенный текст и любые вложенные элементы будут удалены.
Это отличие от интерполяций:
<p>
Статический текст и {{ message }}
</p>
Здесь статический текст останется, а вместо {{ message }} подставится значение поля.
Невозможность смешивать статический текст и v-text
Поскольку v-text полностью контролирует textContent, вы не можете удобно "дописать" к нему статический текст внутри одного и того же элемента. Например, так сделать не получится:
<p v-text="'Имя - ' + user.name"></p> <!-- Так можно -->
<p v-text="user.name">
(активный) <!-- Этот текст удалится -->
</p>
Если вам нужно одновременно вывести динамический и статический текст, есть три подхода:
Собрать строку в самом выражении:
<p v-text="user.name + ' (активный)'"></p>Вынести в вычисляемое свойство:
computed: { userStatus() { // Собираем строку в логике компонента return `${this.user.name} (активный)` } }<p v-text="userStatus"></p>Использовать интерполяцию вместо v-text:
<p>{{ user.name }} (активный)</p>
На практике именно третий вариант чаще всего и применяют, а v-text оставляют для более специфичных случаев.
v-text и безопасность вывода (XSS, HTML и т.д.)
v-text выводит только текст
Важное качество v-text в том, что оно всегда устанавливает textContent. Это означает:
- HTML-теги внутри строки будут экранированы и показаны как текст, а не как разметка.
- Встроенный JavaScript через атрибуты вроде
onclickили через<script>теги не будет выполнен.
Давайте разберем пример:
<div id="app">
<!-- Вывод через v-text -->
<p v-text="raw"></p>
<!-- Для сравнения - интерполяция выводит то же самое как текст -->
<p>{{ raw }}</p>
<!-- Для сравнения с v-html - этот вариант опаснее -->
<p v-html="raw"></p>
</div>
<script>
const app = Vue.createApp({
data() {
return {
raw: '<strong>Важный текст</strong>' // Строка с HTML-тегами
}
}
})
app.mount('#app')
</script>
Как это отобразится:
- В
<p v-text="raw">вы увидите текст:<strong>Важный текст</strong>. - В
<p>{{ raw }}</p>— то же самое. - В
<p v-html="raw"></p>вы увидите реально жирный текст «Важный текст».
Здесь хорошо видно отличие v-text от v-html:
- v-text всегда безопасно показывает все как обычный текст;
- v-html интерпретирует строку как HTML и может быть источником XSS, если вы выводите непроверенные данные.
Когда v-text помогает избежать ошибок с HTML
Если вы работаете с данными, которые теоретически могут содержать HTML (например, пользовательский ввод или текст из внешнего API), использование v-html без фильтрации опасно. Директива v-text в этой ситуации — более безопасный выбор по умолчанию.
Например, у вас есть комментарий пользователя:
<div id="app">
<p v-text="comment"></p>
</div>
<script>
const app = Vue.createApp({
data() {
return {
comment: '<script>alert("XSS")</script>Привет' // Потенциально опасный ввод
}
}
})
app.mount('#app')
</script>
В результате в DOM появится именно текст <script>alert("XSS")</script>Привет. Скрипт не выполнится, т.к. он не интерпретируется как HTML.
Смотрите, это не заменяет полноценную серверную валидацию, но с точки зрения Vue v-text — безопасный вариант для любых внешних текстов, которые не должны являться HTML-разметкой.
v-text в разных версиях Vue (2 и 3)
Небольшие отличия между Vue 2 и Vue 3
С высокой точки зрения, поведение v-text в Vue 2 и Vue 3 одинаковое:
- директива принимает выражение;
- присваивает результат в
textContent; - реагирует на изменения реактивных данных.
Различия больше касаются окружения (как создается приложение, как подключается Vue и т.д.), а не самой директивы.
Давайте покажу два аналогичных примера, чтобы вы увидели общую картину.
Пример на Vue 2
<div id="app">
<h3 v-text="title"></h3>
<p v-text="description"></p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
new Vue({
el: '#app', // Монтируемся на элемент с id app
data: {
title: 'Карточка товара', // Заголовок
description: 'Описание товара с использованием v-text' // Описание
}
})
</script>
Пример на Vue 3
<div id="app">
<h3 v-text="title"></h3>
<p v-text="description"></p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const app = Vue.createApp({
data() {
return {
title: 'Карточка товара', // Заголовок
description: 'Описание товара с использованием v-text в Vue 3' // Описание
}
}
})
app.mount('#app') // Монтируем приложение
</script>
Как видите, сами шаблоны с v-text не меняются, меняется способ создания приложения.
Возможности выражений внутри v-text
И в Vue 2, и в Vue 3 вы можете использовать в выражении:
- поля из data;
- вычисляемые свойства;
- результаты методов (через вызов метода в выражении, если он возвращает строку);
- тернарные операторы (для условного выбора строки);
- конкатенацию строк.
Например:
<p v-text="isActive ? 'Аккаунт активен' : 'Аккаунт неактивен'"></p>
<p v-text="formatPrice(price)"></p>
methods: {
formatPrice(value) {
// Здесь мы форматируем число как цену
return value.toFixed(2) + ' ₽'
}
}
Важно помнить: в выражении директивы вы не должны производить побочные эффекты (например, менять состояние). Оно должно только вычислять значение для вывода.
Где v-text удобнее интерполяций
Шаблоны внутри атрибутов (DOM-ориентированный синтаксис)
Если вы пишете шаблон не в SFC (Single File Component), а прямо в HTML (например, подключаете Vue через CDN), вы иногда можете столкнуться с ситуацией, когда интерполяции {{ }} конфликтуют с другим шаблонизатором или с серверной логикой (например, на стороне бэкенда тоже используются {{ }}).
В таких случаях v-text помогает обойтись без фигурных скобок в HTML.
Например, если какой-то серверный шаблонизатор тоже использует {{ }} и перехватывает их, вы можете вместо:
<p>{{ message }}</p>
написать:
<p v-text="message"></p>
И уже не будет конфликта за синтаксис.
Удаление "мигания" шаблона до инициализации Vue
Иногда при медленной загрузке Vue-приложения пользователи могут на долю секунды увидеть "сырые" шаблоны, вроде:
<p>{{ message }}</p>
до того, как Vue успеет эти интерполяции обработать. С v-cloak эту проблему обычно решают, но есть и другой путь — использовать v-text, если вам нужно избежать фигурных скобок в DOM до инициализации.
Однако, честно говоря, v-text для этого применяют не так часто, так как есть более прямые решения, но иногда это помогает.
Ситуации с вложенностью и минимизацией шаблона
Еще один пример — если вы хотите, чтобы элемент был полностью контролируем только одним значением без дополнительного статического текста или дочерних элементов. Тогда v-text хорошо показывает, что этот узел целиком "занят" динамическим текстом и не будет содержать ничего лишнего.
Например:
<!-- Четко видно, что весь <h1> управляется одной строкой -->
<h1 v-text="pageTitle"></h1>
По сравнению с:
<h1>{{ pageTitle }}</h1>
с точки зрения читаемости оба варианта похожи, но в команде могут предпочесть более явный стиль с директивами.
v-text и производительность
Локальная оптимизация
С точки зрения производительности между {{ message }} и v-text="message" разница минимальна и в большинстве приложений вообще не ощущается. Vue и так эффективно отслеживает изменения зависимостей и обновляет только те узлы, которые нужно.
Однако директива v-text иногда может быть слегка проще для движка, т.к. она прямо указывает точку модификации (textContent) и не требует парсинга текста с несколькими интерполяциями.
Например, вот такой шаблон:
<p>{{ firstName }} {{ lastName }} – {{ role }}</p>
вызывает больше работы, чем:
<p v-text="fullUserLabel"></p>
где fullUserLabel — вычисляемое свойство, которое собирает строку заранее.
Но выбор лучше делать не по микропроизводительности, а по удобству чтения и поддержки кода.
Когда v-text не дает выигрыша
Если вы где-то читали, что директивы быстрее интерполяций — в общем случае это не то, на чем стоит строить архитектуру. Vue 2 и Vue 3 довольно эффективно работают и с тем, и с другим, а структура приложения, разделение на компоненты и подход к состоянию дадут вам гораздо больший прирост отзывчивости, чем выбор между v-text и {{ }}.
Используйте v-text там, где он делает шаблон более прозрачным или решает конкретную техническую задачу (конфликт шаблонизаторов, безопасность текста и т.д.).
Практические примеры использования v-text
Пример 1. Вывод пользовательского ввода как текста
Представим форму, где пользователь вводит комментарий. Комментарий мы хотим показывать под формой, но строго как текст, без HTML.
<div id="app">
<label>
Ваш комментарий:
<textarea v-model="comment"></textarea> <!-- Двусторонняя привязка -->
</label>
<h3>Предпросмотр:</h3>
<p v-text="comment"></p> <!-- Показываем введенный текст безопасно -->
</div>
<script>
const app = Vue.createApp({
data() {
return {
comment: '' // Текущее значение текстового поля
}
}
})
app.mount('#app')
</script>
Комментарии в коде показывают роль каждой части:
- v-model — для ввода и обновления данных;
- v-text — для одностороннего безопасного вывода.
Пример 2. Локализация и словари сообщений
Допустим, у вас есть объект переводов и вы хотите выводить текст по ключу.
<div id="app">
<p v-text="messages[currentLang].greeting"></p>
</div>
<script>
const app = Vue.createApp({
data() {
return {
currentLang: 'ru', // Текущий язык
messages: {
ru: {
greeting: 'Здравствуйте' // Приветствие на русском
},
en: {
greeting: 'Hello' // Приветствие на английском
}
}
}
}
})
app.mount('#app')
</script>
Здесь v-text упрощает вывод значения из вложенного словаря, особенно если вы используете "чистый" HTML-шаблон.
Пример 3. Форматирование чисел и дат через методы
Сейчас покажу, как использовать v-text с методами форматирования.
<div id="app">
<p v-text="formatPrice(price)"></p>
<p v-text="formatDate(createdAt)"></p>
</div>
<script>
const app = Vue.createApp({
data() {
return {
price: 1234.5, // Цена в виде числа
createdAt: '2024-10-01' // Дата в формате ISO
}
},
methods: {
formatPrice(value) {
// Округляем до двух знаков и добавляем валюту
return value.toFixed(2) + ' ₽'
},
formatDate(value) {
// Преобразуем строку в объект Date
const date = new Date(value)
// Форматируем в читабельный вид
return date.toLocaleDateString('ru-RU')
}
}
})
app.mount('#app')
</script>
Здесь v-text просто берет результат методов и безопасно выводит как текст.
В чём разница между v-text, v-html и интерполяциями
Сводное сравнение
Чтобы систематизировать, давайте перечислим основные отличия.
v-text
- Заменяет
textContentэлемента. - Удаляет всё внутреннее содержимое (включая HTML-разметку).
- Воспринимает значение только как текст.
- Безопасен с точки зрения HTML/XSS, если не использовать дополнительные обходные маневры.
- Не позволяет смешивать статический и динамический текст внутри одного тега без конкатенации.
Интерполяции {{ }}
- Встраиваются внутрь текстового содержимого, позволяют комбинировать статический и динамический текст.
- Могут использоваться внутри атрибутов, если вы работаете с шаблонизатором Vue на этапе сборки (например, в SFC).
- Тоже выводят значения как текст (если это обычная интерполяция, а не
v-html). - Не затирают всё содержимое элемента, а только заменяют конкретное место интерполяции.
v-html
- Интерпретирует строку как HTML-разметку.
- Может отрисовать тег
<strong>,<a>и т.д. - Может быть источником XSS, если выводите непроверенные данные.
- Не рекомендуется использовать с пользовательским вводом без очистки.
Практические рекомендации по выбору
Используйте v-text, если:
- вам нужно жестко контролировать, что элемент содержит только динамический текст;
- вы хотите избежать конфликтов с другими шаблонизаторами, использующими {{ }};
- вы работаете с "сырой" HTML-строкой, но показывать её должны как текст.
Используйте интерполяции {{ }}, если:
- вам нужно смешать статический и динамический текст;
- вы хотите компактный и привычный синтаксис;
- вы работаете в обычном шаблоне Vue (например, SFC).
Используйте v-html только если:
- вам действительно нужно отрендерить HTML, который приходит как строка;
- вы уверены в источнике данных или предварительно фильтруете/экранируете HTML.
Заключение
Директива v-text в Vue — простой, но полезный инструмент для вывода текста. Она:
- однозначно говорит, что весь элемент управляется одной текстовой привязкой;
- гарантирует, что значение будет интерпретировано как текст, а не как HTML;
- удобна там, где интерполяции {{ }} по тем или иным причинам неудобны (конфликт шаблонизаторов, требование к структуре шаблона).
По функциональности v-text и интерполяции во многом пересекаются, и во многих проектах вы почти не увидите v-text — разработчики предпочитают краткий и знакомый синтаксис {{ }}. Тем не менее знание того, чем именно директива отличается, помогает осознанно выбирать нужный инструмент:
- для безопасного показа строк — v-text;
- для гибкого компоновки текста — интерполяции;
- для HTML-разметки из строки — v-html (с осторожностью).
Если вы понимаете, как v-text работает с textContent, зачем она полностью очищает содержимое элемента и почему она не интерпретирует HTML, вам будет проще проектировать шаблоны, особенно в сочетании с пользовательским вводом и внешними данными.
Частозадаваемые технические вопросы по теме и ответы
Как с помощью v-text вывести значение по умолчанию, если свойство undefined или null
Вы можете использовать логические операторы или тернарный оператор:
<p v-text="userName || 'Гость'"></p>
<!-- Если userName пустой или undefined - будет показано 'Гость' -->
Или более явно:
<p v-text="userName != null ? userName : 'Гость'"></p>
Главное помнить, что выражение в v-text — это обычное JavaScript-выражение.
Можно ли использовать фильтры (как во Vue 2) вместе с v-text
Во Vue 3 фильтров в шаблонах больше нет, но вы можете сделать то же самое через методы или вычисляемые свойства:
<p v-text="formatName(userName)"></p>
methods: {
formatName(value) {
// Любая логика форматирования строки
return value.trim().toUpperCase()
}
}
Во Vue 2 вы тоже можете предпочесть такой подход вместо фильтров — код становится более явным.
Как вывести часть строки (например, только первые 10 символов) через v-text
Используйте методы строк:
<p v-text="description.slice(0, 10) + '...'"></p>
Или через вычисляемое свойство:
computed: {
shortDescription() {
// Ограничиваем длину строки
return this.description.length > 10
? this.description.slice(0, 10) + '...'
: this.description
}
}
<p v-text="shortDescription"></p>
Можно ли обновлять текст через v-text без реактивных данных (например, только по событию)
Vue рассчитан на работу с реактивным состоянием. Но вы можете:
Хранить значение в data и менять его в обработчике события:
<button @click="setMessage">Обновить текст</button> <p v-text="message"></p>data() { return { message: 'Начальный текст' } }, methods: { setMessage() { // Изменяем состояние компонента this.message = 'Обновленный текст' } }Не использовать v-text и напрямую менять DOM через JavaScript, но это уже вне философии Vue и может привести к конфликтам с виртуальным DOM. Лучше держать текст в реактивном состоянии и обновлять через методы.
Как отобразить текст с переводами строк через v-text
v-text сам по себе не превращает \n в переносы строк. Вы можете:
Заменить
\nна<br>и использовать v-html (учитывая безопасность):<p v-html="formattedText"></p>computed: { formattedText() { // Заменяем символы новой строки на теги <br> return this.rawText.replace(/\n/g, '<br>') } }Или использовать CSS со
white-space: pre-line:<p v-text="rawText" style="white-space: pre-line;"></p>В этом случае
\nбудут отображаться как переносы строк без использования HTML. Такой способ безопаснее и хорошо работает с v-text.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

Vue 3 и Pinia
Антон Ларичев
TypeScript с нуля
Антон Ларичев