Основы реактивности
Вакансии vuejobs.com
Выбор API
Эта страница и многие другие главы в этом руководстве, содержат различный контент для Options API и Composition API. В настоящее время выбран Composition API. Можно переключаться между двумя API с помощью переключателя "Выбрать API" в верхней части левой боковой панели.
Объявление реактивного состояния
Возможно создать реактивный объект или массив с помощью функции reactive():
js
import { reactive } from 'vue'
const state = reactive({ count: 0 })
Реактивные объекты это JavaScript Прокси и ведут себя так же, как обычные объекты. Разница в том, что Vue способен отслеживать доступ к свойствам и мутации реактивного объекта. Подробнее о том, как работает система реактивности Vue, объясняется в разделе Реактивность в деталях - но лучше приступать к нему уже после того, как закончите изучать основное руководство.
См. также: Типизированная реактивность
Чтобы использовать реактивное состояние в шаблоне компонента, объявите и верните его из функции компонента setup():
js
import { reactive } from 'vue'
export default {
// `setup` это специальный хук, предназначенный для composition API.
setup() {
const state = reactive({ count: 0 })
// передайте состояние шаблону
return {
state
}
}
}
template
<div>{{ state.count }}</div>
Аналогично, можно объявить функции, которые изменяют реактивное состояние, в той же области видимости и передать их в качестве методов вместе с состоянием:
js
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
// не забудьте также передать функцию.
return {
state,
increment
}
}
}
Переданные методы обычно используются в качестве прослушивателей событий:
template
<button @click="increment">
{{ state.count }}
</button>
<script setup>
Описание всего состояния и всех методов через setup() может быть утомительным и усложнит код. К счастью, это необходимо только тогда, когда не используется шаг сборки. При использовании однофайловых компонентов, можно значительно упростить процесс с помощью <script setup>:
vue
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
function increment() {
state.count++
}
</script>
<template>
<button @click="increment">
{{ state.count }}
</button>
</template>
Импорты верхнего уровня и переменные, объявленные в <script setup>, автоматически можно использовать в шаблоне того же компонента.
В остальной части руководства в основном используется синтаксис однофайловых компонентов +
<script setup>для примеров кода Composition API, так как это наиболее распространенный подход у разработчиков Vue.
Обновление DOM
Когда изменяется реактивное состояние, DOM обновляется автоматически. Однако следует отметить, что обновления DOM не применяются синхронно. Вместо этого Vue буферизирует их до "следующего тика" в цикле обновления, чтобы гарантировать, что каждый компонент обновляется только один раз, независимо от того, сколько изменений состояния было сделано.
Чтобы дождаться завершения обновления DOM после изменения состояния, следует использовать функцию nextTick() глобального API:
js
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// доступ к обновленному DOM
})
}
Подробнее о реактивности
В Vue состояние по умолчанию является глубоко реактивным. Это означает, что можно ожидать отслеживания изменений, даже если они затрагивают вложенные объекты или массивы:
js
import { reactive } from 'vue'
const obj = reactive({
nested: { count: 0 },
arr: ['foo', 'bar']
})
function mutateDeeply() {
// будут работать так, как ожидается.
obj.nested.count++
obj.arr.push('baz')
}
Также можно явно создать неглубоко реактивные объекты где реактивность отслеживается только на корневом уровне, но они, как правило, нужны только в продвинутых случаях использования.
Реактивный прокси и оригинальный объект
Важно отметить, что возвращаемое значение от reactive() является прокси оригинального объекта, который не равен исходному объекту:
js
const raw = {}
const proxy = reactive(raw)
// прокси НЕ РАВЕН оригиналу.
console.log(proxy === raw) // false
Только прокси является реактивным - изменение исходного объекта не вызовет обновлений. Поэтому лучшей практикой при работе с системой реактивности Vue является исключительное использование проксированных версий состояния.
Чтобы обеспечить последовательный доступ к прокси, вызов reactive() на одном и том же объекте будет всегда возвращать один и тот же прокси, а вызов reactive() на существующем прокси будет возвращать этот же прокси:
js
// вызов reactive() на том же объекте, возвращает тот же прокси
console.log(reactive(raw) === proxy) // true
// вызов reactive() на прокси возвращает этот же прокси
console.log(reactive(proxy) === proxy) // true
Это правило распространяется и на вложенные объекты. Из-за глубокой реактивности, вложенные объекты внутри реактивного объекта также являются прокси:
js
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
Ограничения reactive()
API reactive() имеет два ограничения:
Он работает только для объектных типов (объекты, массивы, и другие типы коллекций такие
MapиSet). Он не может хранить примитивные типы такие какstring,numberилиboolean.Поскольку отслеживание реактивности в Vue работает через доступ к свойствам, необходимо всегда сохранять одну и ту же ссылку на реактивный объект. Это означает, что нельзя легко "заменить" реактивный объект, поскольку связь реактивности с первой ссылкой теряется:
jslet state = reactive({ count: 0 }) // вышеуказанная ссылка ({ count: 0 }) больше не отслеживается (реактивность потеряна!) state = reactive({ count: 1 })Это также означает, что когда происходит присваивание или деструктуризация свойства реактивного объекта в локальные переменные, или когда это свойство передается в функцию, тогда теряется связь с реактивностью:
jsconst state = reactive({ count: 0 }) // n локальная переменная, которая независима // от state.count. let n = state.count // не влияет на state.count n++ // count после деструктуризации // также независима от state.count. let { count } = state // не влияет на state.count count++ // функция получает простое число и // не сможет отслеживать изменения в state.count callSomeFunction(state.count)
Реактивные переменные ref()
Для устранения ограничений reactive(), Vue также предоставляет функцию ref() которая позволяет создавать реактивные "ref-ссылки", которые могут содержать значения любого типа:
js
import { ref } from 'vue'
const count = ref(0)
ref() принимает аргумент и возвращает его завернутым в объект ref-ссылки со свойством .value:
js
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
См. также: Типизированные ref-ссылки
Аналогично свойствам реактивного объекта, свойство .value ref-ссылки является реактивным. В дополнение, при хранении объектных типов, функция ref автоматически преобразует его .value с помощью reactive().
Ref-ссылка, содержащая значение объекта, позволяет реактивно заменить весь объект:
js
const objectRef = ref({ count: 0 })
// это работает реактивно
objectRef.value = { count: 1 }
Ref-ссылки также могут быть переданы в функции или деструктурированы из простых объектов без потери реактивности:
js
const obj = {
foo: ref(1),
bar: ref(2)
}
// функция получает ссылку
// получает доступ к значению через .value
// но она сохранит реактивность с obj.foo
callSomeFunction(obj.foo)
// значения остаются реактивными
const { foo, bar } = obj
Другими словами, ref() позволяет создать "ref-ссылку" на любое значение и передать его дальше без потери реактивности. Эта очень важно, поскольку часто используется при извлечении логики в функции композиции.
Ref-ссылки в шаблоне
Когда ref-ссылки используются как свойства верхнего уровня в шаблоне, они автоматически "разворачиваются", поэтому нет необходимости использовать .value. Вот предыдущий пример счетчика, использующий вместо этого ref():
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">
{{ count }} <!-- указывать .value больше не нужно -->
</button>
</template>
Обратите внимание, что разворачивание применяется только в том случае, если ссылка является свойством верхнего уровня в контексте рендеринга шаблона. В качестве примера, foo является свойством верхнего уровня, а object.foo - нет.
Имеется объект:
js
const object = { foo: ref(1) }
Следующее выражение НЕ БУДЕТ работать так, как ожидается:
template
{{ object.foo + 1 }}
Результат рендеринга будет [object Object], потому что object.foo - это объект ref-ссылки. Можно исправить это, сделав foo свойством верхнего уровня:
js
const { foo } = object
template
{{ foo + 1 }}
Теперь результат рендеринга будет 2.
Следует отметить, что ref-ссылка также будет развернута, если это окончательное вычисленное значение текстовой интерполяции (т.е. тег {{ }}), поэтому следующее отобразит 1:
template
{{ object.foo }}
Это всего лишь удобная функция интерполяции текста и эквивалентна {{ object.foo.value }}.
Ref-ссылка в реактивном объекте
Когда к ref-ссылке обращаются или изменяют как свойство реактивного объекта, оно также автоматически разворачивается, чтобы вести себя как обычное свойство:
js
const count = ref(0)
const state = reactive({
count
})
console.log(state.count) // 0
state.count = 1
console.log(count.value) // 1
Если новая ref-ссылка назначается свойству, связанному с существующей ref-ссылкой, она заменяет старую ref-ссылку:
js
const otherCount = ref(2)
state.count = otherCount
console.log(state.count) // 2
// старая ref-ссылка теперь не влияет на state.count
console.log(count.value) // 1
Разворачивание ref-ссылки происходит только при вложении внутри глубокого реактивного объекта. Он не применяется, когда к нему обращаются как к свойству неглубокого реактивного объекта.
Ref-ссылка в массивах и коллекциях
В отличие от реактивных объектов, не происходит разворачивания, когда ref-ссылка доступна как элемент реактивного массива или нативной коллекции, например Map:
js
const books = reactive([ref('Vue 3 Guide')])
// нужно обращаться к .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// нужно обращаться к .value
console.log(map.get('count').value)
Трансформация реактивности
Необходимость использовать .value с ref-ссылками — недостаток, налагаемый ограничениями JavaScript. Однако, благодаря трансформации во время компиляции, можно улучшить эргономику, автоматически добавляя .value в соответствующих местах. Vue предоставляет возможность преобразования во время компиляции, что позволяет написать предыдущий пример "счетчика" следующим образом:
vue
<script setup>
let count = $ref(0)
function increment() {
// нет необходимости в .value
count++
}
</script>
<template>
<button @click="increment">{{ count }}</button>
</template>
Подробнее о трансформации реактивности вы можете узнать в специальном разделе. Обратите внимание, что в настоящее время он все еще является экспериментальным и может измениться.