Связывание данных, перезагрузка и реактивность в 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. Надеюсь, эта статья прояснила систему.