# Основы реактивности

В разделе в примерах кода используется синтаксис однофайловых компонентов

# Объявление реактивного состояния

Реактивное состояние из объекта JavaScript создаётся с помощью метода reactive:

import { reactive } from 'vue'

// реактивное состояние
const state = reactive({
  count: 0
})
1
2
3
4
5
6

Метод reactive — эквивалент Vue.observable() API во Vue 2.x, переименованный чтобы избежать путаницы с observables из RxJS. Возвращаемое состояние здесь будет являться реактивным объектом. Реактивное преобразование «глубокое» — оно будет влиять на все вложенные свойства переданного объекта.

Основной сценарий использования реактивного состояния во Vue — использование во время отрисовки. Благодаря отслеживанию зависимостей, представление будет обновляться автоматически при изменениях реактивного состояния.

Это сама суть системы реактивности Vue. Когда в компоненте возвращается объект из data(), то под капотом он уже становится реактивным с помощью reactive(). Шаблон будет скомпилирован в render-функцию, которая использует эти реактивные свойства.

Подробнее о методе reactive можно узнать в разделе Основы API реактивности.

# Создание автономных ссылок на реактивные значения

Представьте случай, когда есть отдельное примитивное значение (например, строка) и необходимо сделать её реактивной. Конечно, можно сделать объект с одним свойством, значением которого будет эта строка, а затем передать его в reactive. Но для этого у Vue уже есть метод, который сделает то же самое — ref:

import { ref } from 'vue'

const count = ref(0)
1
2
3

ref вернёт реактивный объект, который можно изменять и который служит реактивной ссылкой (ref, от слова reference) для внутреннего значения, которое он хранит — откуда и происходит его имя. Этот объект содержит только одно свойство с именем value:

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1
1
2
3
4
5
6
7

# Разворачивание ref-ссылок

Когда ref-ссылка возвращается в качестве свойства для контекста отрисовки (объект, возвращаемый из setup()) и доступ к свойству осуществляется в шаблоне, то ссылка будет автоматически разворачиваться во внутреннее значение. Указывать .value в шаблоне требуется только для вложенных ref-ссылок:

<template>
  <div>
    <span>{{ count }}</span>
    <button @click="count ++">Увеличить счётчик</button>
    <button @click="nested.count.value ++">Увеличить вложенный счётчик</button>
  </div>
</template>

<script>
  import { ref } from 'vue'
  export default {
    setup() {
      const count = ref(0)
      return {
        count,

        nested: {
          count
        }
      }
    }
  }
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Совет

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

nested: reactive({
  count
})
1
2
3

# Доступ в реактивных объектах

При доступе к ref или мутировании как свойства реактивного объекта, автоматически она будет разворачиваться во внутреннее значение и вести себя как обычное свойство:

const count = ref(0)
const state = reactive({
  count
})

console.log(state.count) // 0

state.count = 1
console.log(count.value) // 1
1
2
3
4
5
6
7
8
9

Если новая ссылка присваивается к свойству, связанному с существующей ссылкой, то она просто заменит собой старую ссылку:

const otherCount = ref(2)

state.count = otherCount
console.log(state.count) // 2
console.log(count.value) // 1
1
2
3
4
5

Разворачивание ссылки происходит лишь в том случае, если она вложена в реактивный Object. Никакого разворачивания выполняться не будет, когда ссылка предоставляет доступ к Array или нативной коллекции, например Map (opens new window):

const books = reactive([ref('Руководство по Vue 3')])
// потребуется указывать .value
console.log(books[0].value)

const map = reactive(new Map([['count', ref(0)]]))
// потребуется указывать .value
console.log(map.get('count').value)
1
2
3
4
5
6
7

# Деструктурирование реактивного состояния

При необходимости использовать лишь несколько свойств из большого реактивного объекта, может возникнуть соблазн воспользоваться деструктурированием из ES6 (opens new window) для получения нужных свойств:

import { reactive } from 'vue'

const book = reactive({
  author: 'Команда Vue',
  year: '2021',
  title: 'Руководство Vue 3',
  description: 'Вы его читаете прямо сейчас ;)',
  price: 'free'
})

let { author, title } = book
1
2
3
4
5
6
7
8
9
10
11

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

import { reactive, toRefs } from 'vue'

const book = reactive({
  author: 'Команда Vue',
  year: '2021',
  title: 'Руководство Vue 3',
  description: 'Вы его читаете прямо сейчас ;)',
  price: 'free'
})

let { author, title } = toRefs(book)

title.value = 'Детальное руководство по Vue 3' // нужно .value потому что title теперь ссылка
console.log(book.title) // 'Детальное руководство по Vue 3'
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Подробнее о refs можно узнать в разделе API реактивных ссылок.

# Предотвращение изменений реактивных объектов с помощью readonly

Иногда необходимо отслеживать изменения реактивного объекта (ref или reactive), но при этом запретить его изменения из определённого места приложения. Например, когда используем реактивный объект, внедряемый через provide, и хотим предотвратить попытки изменений там, где он будет внедряться. Для таких случаев можно создать прокси «только для чтения» исходного объекта:

import { reactive, readonly } from 'vue'

const original = reactive({ count: 0 })

const copy = readonly(original)

// изменение оригинала будет вызывать наблюдатели, которые отслеживают копию
original.count++

// изменение копии не пройдёт и будет отображено предупреждение
copy.count++ // warning: "Set operation on key 'count' failed: target is readonly."
1
2
3
4
5
6
7
8
9
10
11