Computed, Methods и Watchers в Vue.js
Введение
В Vue есть три способа работы с реактивными данными: computed, methods и watchers. Каждый имеет свое назначение и особенности.
Computed Properties (Вычисляемые свойства)
Computed — это свойства, которые вычисляются на основе других реактивных данных и кэшируются.
Composition API
<template>
<p>Имя: {{ firstName }}</p>
<p>Фамилия: {{ lastName }}</p>
<p>Полное имя: {{ fullName }}</p>
</template>
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Иван')
const lastName = ref('Иванов')
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
</script>
Options API
<script>
export default {
data() {
return {
firstName: 'Иван',
lastName: 'Иванов'
}
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
}
</script>
Особенности computed
Кэширование
Результат вычисляется только при изменении зависимостей:
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const doubleCount = computed(() => {
console.log('Вычисление...') // Вызовется только при изменении count
return count.value * 2
})
</script>
<template>
<p>{{ doubleCount }}</p>
<p>{{ doubleCount }}</p>
<p>{{ doubleCount }}</p>
<!-- console.log вызовется только один раз -->
</template>
Writable Computed (запись)
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Иван')
const lastName = ref('Иванов')
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
// Использование
fullName.value = 'Петр Петров'
// firstName.value = 'Петр'
// lastName.value = 'Петров'
</script>
Methods (Методы)
Methods — это функции компонента, которые выполняются каждый раз при вызове.
Composition API
<template>
<p>{{ getFullName() }}</p>
<button @click="greet">Поздороваться</button>
</template>
<script setup>
import { ref } from 'vue'
const firstName = ref('Иван')
const lastName = ref('Иванов')
function getFullName() {
console.log('Метод вызван')
return `${firstName.value} ${lastName.value}`
}
function greet() {
alert(`Привет, ${getFullName()}!`)
}
</script>
Options API
<script>
export default {
data() {
return {
firstName: 'Иван',
lastName: 'Иванов'
}
},
methods: {
getFullName() {
return `${this.firstName} ${this.lastName}`
},
greet() {
alert(`Привет, ${this.getFullName()}!`)
}
}
}
</script>
Особенности methods
Нет кэширования
<template>
<p>{{ getFullName() }}</p>
<p>{{ getFullName() }}</p>
<p>{{ getFullName() }}</p>
<!-- console.log вызовется 3 раза -->
</template>
<script setup>
function getFullName() {
console.log('Метод вызван')
return `${firstName.value} ${lastName.value}`
}
</script>
Принимают аргументы
<template>
<p>{{ formatPrice(100) }}</p>
<p>{{ formatPrice(250, 'USD') }}</p>
</template>
<script setup>
function formatPrice(price, currency = 'RUB') {
return `${price} ${currency}`
}
</script>
Watchers (Наблюдатели)
Watchers — это функции, которые реагируют на изменения данных и выполняют побочные эффекты.
Composition API
<template>
<input v-model="searchQuery" placeholder="Поиск..." />
</template>
<script setup>
import { ref, watch } from 'vue'
const searchQuery = ref('')
watch(searchQuery, (newValue, oldValue) => {
console.log(`Изменилось с "${oldValue}" на "${newValue}"`)
// Выполнение запроса к API
fetchResults(newValue)
})
function fetchResults(query) {
// API запрос
}
</script>
Options API
<script>
export default {
data() {
return {
searchQuery: ''
}
},
watch: {
searchQuery(newValue, oldValue) {
console.log(`Изменилось с "${oldValue}" на "${newValue}"`)
this.fetchResults(newValue)
}
},
methods: {
fetchResults(query) {
// API запрос
}
}
}
</script>
Особенности watchers
Наблюдение за несколькими источниками
<script setup>
import { ref, watch } from 'vue'
const firstName = ref('')
const lastName = ref('')
watch([firstName, lastName], ([newFirst, newLast]) => {
console.log(`Полное имя: ${newFirst} ${newLast}`)
})
</script>
Немедленное выполнение
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue) => {
console.log('Count изменился:', newValue)
}, { immediate: true })
// Вызовется сразу при создании
</script>
Глубокое наблюдение
<script setup>
import { reactive, watch } from 'vue'
const user = reactive({
name: 'Иван',
address: {
city: 'Москва'
}
})
watch(user, (newValue) => {
console.log('User изменился:', newValue)
}, { deep: true })
// Сработает даже при изменении user.address.city
user.address.city = 'Санкт-Петербург'
</script>
watchEffect
Автоматически отслеживает зависимости:
<script setup>
import { ref, watchEffect } from 'vue'
const count = ref(0)
const doubled = ref(0)
watchEffect(() => {
doubled.value = count.value * 2
console.log(`Count: ${count.value}, Doubled: ${doubled.value}`)
})
// Автоматически отслеживает count
</script>
Сравнение
Computed vs Methods
| Свойство | Computed | Methods |
|---|---|---|
| Кэширование | Да | Нет |
| Реактивность | Автоматическая | Ручная |
| Аргументы | Нет | Да |
| Использование в template | {{ computed }} | {{ method() }} |
| Побочные эффекты | Не рекомендуется | Можно |
Когда использовать computed:
- Вычисление на основе других данных
- Нужно кэширование
- Синхронные операции
Когда использовать methods:
- Обработка событий
- Нужны параметры
- Побочные эффекты (API запросы, изменение данных)
Computed vs Watchers
| Свойство | Computed | Watchers |
|---|---|---|
| Цель | Вычисление значения | Побочные эффекты |
| Результат | Возвращает значение | Не возвращает |
| Зависимости | Автоматические | Явные |
| Асинхронность | Нет | Да |
Когда использовать computed:
- Нужно вычислить новое значение
- Зависимости определяются автоматически
- Синхронные операции
Когда использовать watchers:
- API запросы
- Работа с localStorage
- Сложная асинхронная логика
- Нужен доступ к старому и новому значению
Практические примеры
Фильтрация списка (computed)
<template>
<input v-model="searchQuery" placeholder="Поиск..." />
<ul>
<li v-for="user in filteredUsers" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
<script setup>
import { ref, computed } from 'vue'
const searchQuery = ref('')
const users = ref([
{ id: 1, name: 'Иван' },
{ id: 2, name: 'Петр' },
{ id: 3, name: 'Мария' }
])
const filteredUsers = computed(() => {
return users.value.filter(user =>
user.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
})
</script>
API запрос (watcher)
<template>
<input v-model="userId" type="number" placeholder="User ID" />
<div v-if="loading">Загрузка...</div>
<div v-else-if="userData">{{ userData.name }}</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const userId = ref(1)
const userData = ref(null)
const loading = ref(false)
watch(userId, async (newId) => {
loading.value = true
try {
const response = await fetch(`/api/users/${newId}`)
userData.value = await response.json()
} finally {
loading.value = false
}
})
</script>
Форматирование (method с аргументами)
<template>
<div v-for="product in products" :key="product.id">
<p>{{ product.name }}</p>
<p>{{ formatPrice(product.price) }}</p>
<p>{{ formatPrice(product.price, 'USD') }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
const products = ref([
{ id: 1, name: 'Товар 1', price: 100 },
{ id: 2, name: 'Товар 2', price: 250 }
])
function formatPrice(price, currency = 'RUB') {
return new Intl.NumberFormat('ru-RU', {
style: 'currency',
currency
}).format(price)
}
</script>
Сохранение в localStorage (watcher)
<script setup>
import { ref, watch } from 'vue'
const settings = ref({
theme: 'light',
language: 'ru'
})
// Загрузка из localStorage
const saved = localStorage.getItem('settings')
if (saved) {
settings.value = JSON.parse(saved)
}
// Сохранение при изменении
watch(settings, (newSettings) => {
localStorage.setItem('settings', JSON.stringify(newSettings))
}, { deep: true })
</script>
Частые ошибки
Использование methods вместо computed
<!-- Неэффективно -->
<template>
<p>{{ getFullName() }}</p>
<p>{{ getFullName() }}</p>
<!-- Вызовется дважды -->
</template>
<!-- Эффективно -->
<template>
<p>{{ fullName }}</p>
<p>{{ fullName }}</p>
<!-- Вычислится один раз -->
</template>
<script setup>
import { computed } from 'vue'
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
</script>
Побочные эффекты в computed
<!-- Неправильно -->
<script setup>
import { computed, ref } from 'vue'
const count = ref(0)
const doubled = ref(0)
const bad = computed(() => {
doubled.value = count.value * 2 // Побочный эффект!
return count.value * 2
})
</script>
<!-- Правильно - используйте watcher -->
<script setup>
import { watch, ref } from 'vue'
const count = ref(0)
const doubled = ref(0)
watch(count, (newCount) => {
doubled.value = newCount * 2
})
</script>
Синхронные операции в watcher
<!-- Неэффективно -->
<script setup>
const firstName = ref('')
const lastName = ref('')
const fullName = ref('')
watch([firstName, lastName], ([first, last]) => {
fullName.value = `${first} ${last}`
})
</script>
<!-- Эффективно - используйте computed -->
<script setup>
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`
})
</script>
Вывод
Computed:
- Вычисление значений на основе других данных
- Автоматическое кэширование
- Синхронные операции
Methods:
- Обработка событий
- Функции с параметрами
- Побочные эффекты
Watchers:
- Реакция на изменения
- Асинхронные операции
- Побочные эффекты (API, localStorage)
На собеседовании:
Важно уметь:
- Объяснить разницу между computed, methods и watchers
- Рассказать о кэшировании в computed
- Привести примеры, когда использовать каждый вариант
- Объяснить проблемы производительности при неправильном выборе