Как принудительно пере-отобразить (re-render) компонент Vue
Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue обновляет DOM всякий раз, когда изменяется состояние определенное в JavaScript. Однако в некоторых сценариях этого подхода недостаточно, и нам нужно повторно визуализировать отдельные компоненты.
В этой статье мы рассмотрим некоторые рекомендации по эффективному повторному рендерингу компонентов Vue, чтобы избежать перезагрузки всей страницы. Следующие четыре основных метода применимы как к Vue 2, так и к Vue 3. Давайте начнем!
- Hot reload (Горячая перезагрузка)
- Хак Vue v-if
- Встроенный в Vue метод forceUpdate
- Техника смены ключей
Hot reload (Горячая перезагрузка)
Горячая перезагрузка включает в себя больше, чем просто перезагрузка страницы всякий раз, когда мы обновляем файл компонента. Когда мы изменяем компонент в файле *.vue с включенной горячей перезагрузкой, Vue заменяет все экземпляры этого компонента без обновления страницы. Поэтому он сохраняет текущее состояние нашего приложения, а также замененные компоненты, тем самым улучшая работу разработчика всякий раз, когда вы значительно изменяете шаблоны или стили компонентов.
Использование горячей перезагрузки
Всякий раз, когда вы формируете проект с помощью Vue CLI, горячая перезагрузка будет включена по умолчанию. Кроме того, когда вы настраиваете новый проект вручную, горячая перезагрузка включается автоматически, когда вы обслуживаете свой проект с помощью webpack-dev-server --hot
.
Для расширенных вариантов использования я рекомендую использовать vue-hot-reload-api, который используется внутри Vue Loader.
Отключение горячей перезагрузки
Горячая перезагрузка всегда включается автоматически, кроме следующих случаев:
- установка атрибута target в webpack со значение
node
(SSR) - указание webpack минимизировать код
process.env.NODE_ENV === 'production'
Чтобы специально отключить горячую перезагрузку, используйте опцию hotReload: false:
module: { rules: [ { test: /\.vue$/, loader: 'vue-loader', options: { hotReload: false // disables Hot Reload } } ] }
Правила сохранения состояния
При повторном рендеринге компонентов процесс горячей перезагрузки следует набору правил сохранения состояния (state preservation rules).
Если вы редактируете <template>
компонента, экземпляры редактированного компонента будут повторно отображаться, сохраняя все существующие частные состояния. Это стало возможным благодаря объединению шаблонов для создания новых функций рендеринга без побочных эффектов.
При изменении раздела <script> компонента экземпляры измененного компонента будут уничтожены и воссозданы заново. Это связано с возможностью побочных эффектов от ловушек жизненного цикла в <script>, требующих перезагрузки, а не повторного рендеринга для обеспечения согласованности.
Поэтому следует проявлять осторожность при использовании таймеров или других глобальных побочных эффектов внутри обработчиков жизненного цикла компонентов. Иногда, если компонент имеет глобальные побочные эффекты, мы должны перезагрузить всю страницу.
Имейте в виду, что горячая перезагрузка не влияет на состояние <style>
приложения, поскольку оно выполняется независимо через vue-style-loader.
Хак Vue v-if
Директива v-if, включенная в библиотеку Vue, отображает компонент только тогда, когда заданное условие истинно. Если оно ложно, компонент не появится в DOM. Поэтому творческое использование директивы v-if может быть лучшим решением.
Пример использования директивы v-if:
<template> <MyComponent v-if="renderComponent" /> </template>
В разделе script
мы добавим метод forceRender
, который использует nextTick
, утилиту для ожидания следующего сброса обновления DOM:
import { nextTick, ref } from 'vue'; const renderComponent = ref(true); const forceRender = async () => { // Here, we'll remove MyComponent renderComponent.value = false; // Then, wait for the change to get flushed to the DOM await nextTick(); // Add MyComponent back in renderComponent.value = true; };
В сценарии, где мы используем Options API вместо Composition API, наш script
будет больше похож на следующий код:
export default { data() { return { renderComponent: true, }; }, methods: { async forceRender() { // Remove MyComponent from the DOM this.renderComponent = false; // Then, wait for the change to get flushed to the DOM await this.$nextTick(); // Add MyComponent back in this.renderComponent = true; } } };
В приведенном выше фрагменте кода для renderComponent
изначально задано значение true
, что означает визуализацию MyComponent
. Всякий раз, когда мы вызываем forceRender
, для renderComponent
устанавливается значение false
. При этом рендеринг MyComponent
прекращается, поскольку директива v-if
теперь оценивается как false
.
Сразу же после того, как MyComponent
прекращает рендеринг, мы ждем галочки, а затем устанавливаем для renderComponent
значение true
. Это также устанавливает значение true
для директивы v-if
, которая отображает обновленный экземпляр MyComponent
.
Два элемента имеют решающее значение для понимания того, как работает nextTick(). Во-первых, мы должны дождаться следующего тика, если только наши обновления renderComponent
не отменятся, и мы не заметим никаких изменений.
В Vue вы можете использовать nextTick()
сразу после изменения состояния, чтобы дождаться завершения обновления DOM. Мы можем либо передать обратный вызов в качестве аргумента, либо дождаться возвращенного промиса.
Vue уничтожит предыдущий компонент, потому что он создает совершенно новый компонент, когда мы визуализируем его во второй раз. В результате наш новый MyComponent
пройдет все свои типичные жизненные циклы, created
, mounted
и т. д.
Встроенный в Vue метод forceUpdate
Как рекомендовано в официальной документации Vue, встроенный метод forceUpdate
— один из лучших способов вручную обновить компонент.
Как правило, Vue обновляет представление в ответ на изменения в зависимостях. Несмотря на то, что ни одна из зависимостей не изменилась, мы все равно можем принудительно выполнить это обновление, вызвав метод forceUpdate
, который заставляет экземпляр компонента выполнить повторную визуализацию.
Однако это противоречит назначению всей системы реактивности (the purpose of the entire reactivity system); поэтому в таких ситуациях такой подход лучше не использовать. Мы можем ошибочно думать, что Vue будет реагировать на изменения определенного свойства или переменной, но система реактивности не всегда так работает.
В приведенном ниже коде показано, как вызвать метод forceUpdate с помощью Vue Options API:
export default { methods: { ForcesUpdateComponent() { // our code this.$forceUpdate(); // Notice we have to use a $ here // our code } } }
Этот метод существует только в экземпляре компонента, поэтому нам нужно проявить немного творчества, чтобы вызвать его с помощью Composition API в Vue 3:
import { getCurrentInstance } from 'vue'; const methodThatForcesUpdate = () => { // our code const instance = getCurrentInstance(); instance.proxy.forceUpdate(); // our code };
Единственный случай, когда мы можем захотеть использовать метод forceUpdate
, — это когда мы явно создали нереактивное состояние компонента с помощью расширенных API-интерфейсов реактивности. Однако, учитывая полностью автоматическую систему реактивности Vue, это необязательно.
Техника смены ключей
Используя метод смены ключа, мы можем предоставит атрибут key
, чтобы сообщить Vue, что определенный компонент подключен к определенному фрагменту данных. Если ключ останется прежним, компонент не изменится. Но если ключ меняется, Vue понимает, что ему нужно удалить предыдущий компонент и создать новый.
Почему Vue требует ключ?
Давайте углубимся в то, что конкретно делает атрибут key
и зачем он нам нужен. Предположим, мы визуализируем список компонентов (например через v-for), который включает один или несколько из следующих элементов:
- локальное состояние (local state)
- процесс инициализации либо с помощью функции настройки Vue 3, либо в созданных и смонтированных хуках при использовании API параметров.
- нереактивные манипуляции с DOM, реализуемые с помощью jQuery или Vanilla API.
При сортировке или обновлении этого списка мы не хотим повторно отображать все что есть в списке; мы хотим перерисовать только те части списка, которые мы обновили. Для этого мы предоставим свойство key
, которое поможет Vue отслеживать, что изменилось, а что нет. Поскольку индекс массива не связан с конкретными элементами нашего списка, его использование в данной ситуации ничего не даст.
Техника изменения ключа считается лучшим способом заставить Vue повторно отобразить компонент. Ниже приведен основной способ проиллюстрировать это:
<template> <MyComponent :key="componentKey" /> </template> <script setup> import { ref } from 'vue'; const componentKey = ref(0); const forceRender = () => { componentKey.value += 1; }; </script>
В приведенном выше фрагменте кода мы добавим атрибут key
в MyComponent
, а затем изменим этот атрибут key
всякий раз, когда нам потребуется повторная визуализация MyComponent
.
При использование Options API, это будет выглядеть следующим образом:
export default { data() { return { componentKey: 0, }; }, methods: { forceRender() { this.componentKey += 1; } } }
Всякий раз, когда вызывается forceRender
, значение componentKey
будет меняться. Когда это произойдет, Vue будет знать, что он должен уничтожить компонент и инициализировать новый. Мы также получим дочерний компонент, который сбросит свое состояние и повторно инициализирует себя.
Заключение
В идеале мы должны иметь возможность использовать надежную систему реактивности Vue и избегать использования таких грубых обходных путей, но бывают случаи, когда мы должны принудительно выполнить повторный рендеринг компонента, чтобы выполнить поставленную задачу. В этой статье мы рассмотрели четыре различных способа принудительного повторного рендеринга компонента Vue, включая горячую перезагрузку, хак v-if
, метод forceUpdate
и, наконец, метод изменения ключа.
Правильный выбор в конечном итоге будет зависеть от уникальной ситуации, в которой вы оказались, однако метод смены ключа обычно является самым безопасным выбором. Надеюсь, вам понравилась эта статья! Обязательно оставьте комментарий, если у вас есть какие-либо вопросы. Удачного кодирования!