Связывание данных, перезагрузка и реактивность в Vue.js

Мои представления Vue иногда отказывались автоматически перерисовывать. После того, как я ударил Vue.$forceUpdate() повсюду в моей кодовой базе, я решил, что этого достаточно, и погрузился в систему реактивности Vue.

Что такое реактивность Vue?

Vue привязывает данные к представлению.

Если вы посмотрите на этот код, вы увидите простой объект JavaScript, содержащий данные . В данном случае это message свойство со значением Hello Vue.js!

<script src="https://unpkg.com/vue"></script>

<div id="app">
  <p>{{ message }}</p>
</div>
<script>
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js!'
  },
})
</script>

Представление - это вывод HTML. В четвертой строке я говорю Vue отображать свойство message. При рендеринге в браузере будет отображаться «Hello Vue.js!».

Теперь, когда вы изменяете свойство message, вступает в игру реактивность. Система реактивности Vue обнаружит изменение, внесенное в message, и обновит HTML, чтобы отобразить новое значение. В старые времена jQuery вам приходилось вручную выбирать правильный узел HTML и обновлять его содержимое.

Это тот же код, что и раньше, но теперь при щелчке по элементу #app свойство сообщения изменит значение на «Вы нажали меня!».

<div @click="message = 'You clicked me!'" id="app">
  <p>{{ message }}</p>
</div>
<script>
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js!'
  },
})
</script>

При нажатии на элемент #app Vue действительно автоматически обновляет представление!

Взгляд под капот

Когда вы устанавливаете объект data, Vue автоматически добавляет геттеры и сеттеры для каждого свойства.

Вы можете убедиться в этом сами, позвонив console.dir(this.$data):

<script>
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js!'
  },

  mounted() {
      console.dir(this.$data);
  }
})
</script>

При открытии консоли вы можете увидеть свойства get message и set message, созданные Vue.

Каждый раз, когда устанавливается значение свойства message, автоматически вызывается функция set message, которая запускает повторную визуализацию представления.

Ловушка 1: массивы

Пока все отлично работает. Если я изменю свойство message, представление будет перерисовано.

Но предположим, что я изменил код на следующий:

<script src="https://unpkg.com/vue"></script>

<div @click="message[0] = 'You clicked me!'" id="app">
  <p>{{ message[0] }}</p>
</div>
<script>
new Vue({
  el: '#app',
  data: {
    message: ['Hello Vue.js!']
  },
})
</script>

Все то же самое, за исключением того, что теперь я сохраняю сообщение в массиве.

Когда я загружаю страницу и нажимаю на элемент, ничего не происходит.

Vue создаст getter и setter для свойства message. Но я обновляю значение в массиве message, а не в самом массиве. Поэтому повторный рендеринг не запускается автоматически.

Ловушка 2: добавление свойств объекта

При щелчке по странице я хочу отобразить прощальное сообщение.

Я сохраняю приветственное сообщение в message.welcome.

<script src="https://unpkg.com/vue"></script>

<div @click="message.goodbye = 'You clicked me - Goodbye!'" id="app">
  <p>{{ message.welcome }}</p>
  <p v-if="message.goodbye">{{ message.goodbye }}</p>
</div>
<script>
new Vue({
  el: '#app',
  data: {
    message: {
        welcome: 'Hello!'
    }
  },
})
</script>

Когда страница будет нажата, я добавлю свойство message.goodbye. Когда я нажимаю на страницу, должно появиться прощальное сообщение, верно?

Но ничего не происходит. Отображается только приветственное сообщение.

Это потому, что Vue добавит getters и setters в начале своего жизненного цикла. Если вы добавите свойство после создания экземпляра Vue, он не узнает об этом. Таким образом, установка message.goodbye не вызовет setter, и поэтому Vue не будет повторно визуализировать.

Решение 1. Принудительное обновление

Одно из решений - использовать $forceUpdate(). Это вызовет повторный рендеринг текущего компонента, включая его дочерние элементы.

Я обновил код примера массива для вызова $forceUpdate() при щелчке по компоненту:

<script src="https://unpkg.com/vue"></script>

<div @click="() => { message[0] = 'You clicked me!'; $forceUpdate(); }" id="app">
  <p>{{ message[0] }}</p>
</div>
<script>
new Vue({
  el: '#app',
  data: {
    message: ['Hello Vue.js!']
  },
})
</script>

Решение 2: Vue Set

Функция Vue.set() добавляет реактивное свойство к объекту или массиву.

К сожалению, вы не можете вызвать функцию Vue.set() из представления. Поэтому я добавляю метод handleClick(). Этот метод устанавливает значение массива message с индексом 0 на "You clicked me!".

<script src="https://unpkg.com/vue"></script>

<div @click="handleClick()" id="app">
  <p>{{ message[0] }}</p>
</div>
<script>
new Vue({
  el: '#app',
  data: {
    message: ['Hello Vue.js!']
  },

  methods: {
      handleClick() {
          Vue.set(this.message, 0, 'You clicked me!');
      }
  }
})
</script>

Точно так же я могу обновить пример кода свойства объекта, чтобы использовать функцию Vue.set().

<script src="https://unpkg.com/vue"></script>

<div @click="handleClick()" id="app">
  <p>{{ message.welcome }}</p>
  <p v-if="message.goodbye">{{ message.goodbye }}</p>
</div>
<script>
new Vue({
  el: '#app',
  data: {
    message: {
        welcome: 'Hello!'
    }
  },

  methods: {
      handleClick() {
          Vue.set(this.message, 'goodbye', 'You clicked me - Goodbye!');
      }
  }
})
</script>

При запуске в браузере представление автоматически перерисовывается!

По моему опыту, это избавляет от головной боли и времени на отладку, если вы полностью понимаете систему реактивности Vue. Надеюсь, эта статья прояснила систему.