Блог

Пишу о PHP, Laravel и Yii2, фрилансе и работе веб–разработчика.

Правильный способ заставить Vue перендерить компонент

Эта публикация является любительским переводом статьи «The correct way to force Vue to re-render a component» от Майкла Тиссена.

Иногда бывает так, что реактивности Vue не хватает, и вам нужно перерендерить компонент. Или вам нужно сбросить DOM и начать сначала.

Так как же правильным образом заставить Vue перерендерить компонент?

Ответ прост. Правильный путь — использовать :key в компоненте. Когда вам нужно перерендерить компонент, вы просто меняете значение ключа и Vue обновит компонент.

Довольно просто, не так ли? Есть также и другие способы:

  • Худший вариант: обновить всю страницу,
  • Плохой вариант: использовать v-if хак,
  • Чуть лучше: использовать встроенный в Vue метод forceUpdate,
  • Лучший вариант: использовать изменение ключа.

Обратите внимание! Если вы столкнулись с ситуацией, в которой вам нужно перерендерить компонент, вероятно, есть более правильный способ решить задачу. Скорее всего, вы не до конца разобрались с чем-то из этого: 1. Реактивность Vue, 2. Computed-свойства, 3. Watch-свойства, 4. Не используете :key атрибут для v-for.

А теперь рассмотрим доступные способы перерендерить компонент. Почти все из них заменяемы на вариант с изменением ключа, он рассмотрен в самом конце статьи.

Худший вариант: перезагрузить все страницу

Этот способ — примерно то же самое, что перезагрузка компьютера каждый раз, когда вы хотите закрыть какое-нибудь приложение. Звучит странно, правда?

Полагаю, это и правда может когда-то вам помочь. Однако это ужасное решение, поэтому не используйте его. Совсем.

Лучше посмотрите, что вы можете использовать ещё.

Плохой вариант: использовать v-if хак

Vue содержит директиву v-if, которая позволяет рендерить компонент или часть компонента только тогда, когда условие возвращает истину. Если в условии ложь, то всё что внутри не отрендерится и не будет существовать в DOM.

Давайте посмотрим, как это выглядит на практике.

Добавьте v-if в ваш шаблон:

<template>
  <my-component v-if="renderComponent" />
</template>

В секции script добавьте метод, который использует функцию nextTick:

<script>
  export default {
    data() {
      return {
        renderComponent: true,
      };
    },
    methods: {
      forceRerender() {
        // Сначала скроем компонент
        this.renderComponent = false;
        
        this.$nextTick(() => {
          // А потом покажем снова
          this.renderComponent = true;
        });
      }
    }
  };
</script>

Вот что происходит в этом коде:

  1. Сначала renderComponent установлено в true, поэтому компонент my-component рендерится
  2. Когда мы вызываем forceRerender, мы сразу устанавливаем renderComponent в false
  3. Мы прекращаем рендеринг my-component, потому что v-if директива содержит ложь
  4. На следующем шаге (nextTick) мы возвращаем renderComponent в true
  5. Теперь v-if содержит истину, поэтому мы снова рендерим компонент my-component

В этом примере есть два важных момента, которые важны для понимания того, как это работает. Первое — мы должны дождаться nextTick, иначе мы не увидим никаких изменений.

В Vue tick (тик) — это один цикл обновления DOM. Vue собирает все обновления в одном тике, и в конце этого тика обновляет то, что отображается в DOM в процессе этих обновлений. Если мы не дождёмся следующего тика, то наши изменения свойства renderComponent перезапишут сами себя и ничего не изменится. Поэтому мы используем nextTick.

Второе, Vue создаст совершенно новый компонент, когда мы отрендерим его второй раз. Vue уничтожит первый компонент и создаст новый. Это значит, что наш новый my-component пройдёт все хуки жизненного цикла компонента — created, mounted и т. д.

Кстати, вы можете использовать nextTick с промисами:

forceRerender() {
  // Удаляем компонент из DOM
  this.renderComponent = false;

  // Если предпочитаете промисы, можете использовать их так
  this.$nextTick().then(() => {
    // Возвращаем компонент обратно
    this.renderComponent = true;
  });
}

Тем не менее, это не очень хорошее решение. Скорее, это хак. Поэтому давайте посмотрим, что ещё мы можем использовать.

Способ получше: можно использовать forceUpdate

Это один из двух способов, который отлично решает проблему. При этом, он официально поддерживается Vue и не является хаком или чем-то подобным.

Обычно Vue реагирует на изменения в зависимостях, обновляя представление. Однако, когда вы запускаете forceUpdate, вы можете принудительно запустить обновление представления, даже если зависимости никак не изменились.

И вот здесь большинство людей делает множество серьёзных ошибок.

Если Vue автоматически обновляет всё когда что-то меняется, то зачем нам самим заставлять его что-то обновлять?

Причина в том, что иногда реактивность Vue может сбивать с толку. Мы думаем, что Vue отреагирует на изменения свойства или переменной, но этого не происходит. Есть также определённые ситуации, когда реактивность Vue вообще не распознаёт ваши изменения.

Стоит отметить, что forceUpdate всё же не лучший способ решения задачи. Используйте его аккуратно.

There are two different ways that you can call forceUpdate, on the component instance itself as well as globally:

// Глобально
import Vue from 'vue';
Vue.forceUpdate();

// Внутри вашего компонента
export default {
  methods: {
    methodThatForcesUpdate() {
      // ...
      this.$forceUpdate();  // Заметьте, что мы используем $ в качестве префикса
      // ...
    }
  }
}

Важно: этот способ не обновляет computed-свойства. Вызов forceUpdate только заставляет шаблон перерендерится.

Лучший способ: изменение ключа

Что за ключ? Сейчас во всё разберёмся. Вообще, существует много ситуаций, когда перерендеринг может действительно вам потребоваться.

Для того, чтобы сделать это надлежащим образом, мы свяжем наш компонент с атрибутом key. Так Vue свяжет компонент с отдельным куском данных. И если ключ при изменениях останется прежним, то Vue не обновит компонент, а вот если ключ поменяется — Vue избавится от старого компонент и создаст новый. То, что нам нужно!

Давайте сперва пробежимся по тому, что такое key и почему нам нужно его использовать.

Зачем нужен атрибут key в Vue?

Давайте представим, что вы рендерите список компонентов, у которых есть одно или несколько из этих свойств:

  • У компонента есть локальный стейт,
  • У компонента есть логика инициализации: что-то, что использует mounted или created хуки,
  • Не реактивные DOM-манипуляции: обычно это jQuery-решения или стандартные JS API.

Если вы отсортируете этот список или обновите его каким-либо образом, вам нужно будет повторно отрендерить все части этого списка. Вряд вы захотите перерендерить вообще все элементы этого списка, лучше затронуть лишь то, что изменилось.

Для помощи Vue в отслеживании таких изменений мы внедряем атрибут key. Использование индекса массива тут не поможет, поскольку индекс не привязан к конкретным объектам в нашем списке.

Here is an example list that we have:

const people = [
  { name: 'Ваня', age: 34 },
  { name: 'Марина', age: 25 },
  { name: 'Николай', age: 51 },
];

Если мы отрендерим список с использованием индексов, то получим это:

<ul>
  <li v-for="(person, index) in people" :key="index">
    {{ person.name }} - {{ index }}
  </li>
</ul>

// Outputs
Ваня - 0
Марина - 1
Николай - 2

Если мы удалим Марину, то получим:

Ваня - 0
Николай - 1

Индекс, который был связан с Николаем изменился, хотя Николай всё ещё Николай. При этом, Николай перерендерится, хотя мы этого не хотели.

В нашей ситуации подойдёт какой-то уникальный ID, давайте добавим его.

const people = [
  { id: 'this-is-an-id', name: 'Ваня', age: 34 },
  { id: 'unique-id', name: 'Марина', age: 25 },
  { id: 'another-unique-id', name: 'Николай', age: 51 },
];

<ul>
  <li v-for="person in people" :key="person.id">
    {{ person.name }} - {{ person.id }}
  </li>
</ul>

Когда мы удаляли Марину из нашего списка, Vue удалил компоненты для Марины и Николая, а затем создал новый компонент для Николая. Теперь, когда Vue знает ID, он сохранит два компонента Вани и Николая, и удалит лишь компонент Марины.

Если мы добавим ещё кого-то в список, то Vue знает, что может сохранить текущие компоненты и просто создать один дополнительный. Это действительно полезно, когда у вас есть более сложные компоненты с логикой, собственным состоянием, имеют логику инициализации или выполняют какие-то манипуляции с DOM.

А теперь давайте продолжим с лучшим методом для принудительного повторного рендеринга компонентов в Vue.

Смена ключа для принудительного повторного рендеринга компонента

Наконец-то, лучший способ принудительного повторного рендеринга компонента (по мнению автора, конечно же).

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

Вот простой пример того, как это работает:

<template>
  <component-to-re-render :key="componentKey" />
</template>

<script>
export default {
  data() {
    return {
      componentKey: 0,
    };
  },
  methods: {
    forceRerender() {
      this.componentKey += 1;  
    }
  }
}
</script>

Каждый раз, когда мы в компоненте вызываем forceRerender, наш пропс componentKey изменяется. Это в свою очередь приводит к тому, что Vue узнаёт когда нужно уничтожить инстанс компонента и создать новый.

В результате вы получаете дочерний компонент, который способен сам себя переинициализировать и сбросить своё состояние. Простой и элегантный способ решения проблемы.

И помните, что если вы столкнулись с ситуацией, когда вам нужно принудительно перерендерить компонент, то вероятно вы делаете что-то не лучшим образом. Если всё же вам нужно как-то перерендерить компонент, то выбирайте способ с привязкой ключа.

Дополнение от переводчика

Стоит упомянуть отдельно Vuex и связанную с ним реактивность. Дело в том, что если у вас в глобальном сторе хранится сложный объект или многомерный массив, то Vue не сможет отслеживать изменения в них, если делать это неправильно. В документации Vue, в описании реактивности, есть информация о том, как работать со сложными объектами и массивами, чтобы достичь реактивности.

Как исправить ошибку «data-vocabulary.org schema deprecated» в Гугл-консоли

Ребята из Гугл недавно анонсировали, что скоро откажутся от поддержки словаря data-vocabulary. Из-за этого многие в консоли начали получать ошибки, связанные с этим. Пока что — это предупреждения, чтобы успеть с этим что-то сделать. Полное отключение запланировано на начало апреля 2020:

«В связи с ростом популярности schema.org, мы решили сфокусироваться на одном варианте микроразметки. С 6-го апреля 2020 data-vocabulary разметка больше не будет отображаться в расширенным сниппетах Гугл.»
Читать далее

Как отключить eslint в vue-cli

Судя по активности обсуждения issue на гитхабе, у многих есть проблема с отключением линтера при работе с vue-cli.

У нас это случилось во время работы с github-actions: мы собирали проект, прогоняли тесты. Локально всё собирается, а в github падает build. Начали выяснять — ругается eslint. Можно настроить всё, но это время. Быстрое решение — отключить линтер, чтобы билд заработал, а потом уже разобраться с линтером, как будет время.

Eslint можно отключить несколькими способами: файл .eslintignore, комментирование части кода в файле webpack.base.conf.js.

С помощью файла .eslintignore

Создаём файл. eslintignore в корне. Внутри файла пишем *, либо игнорим отдельные файлы или папки:

/build/
/config/
/dist/
/*.js
/test/unit/coverage/

Закомменировать настройки в webpack.base.conf.js

Находим в файле build/webpack.base.conf.js настройки, относящиеся к линтеру и комментируем их:

{
  test: /.(js|vue)$/,
  loader: 'eslint-loader',
  enforce: "pre",
  include: [resolve('src'), resolve('test')],
  options: {
    formatter: require('eslint-friendly-formatter')
  }
},

Как сделать редирект в Vue

Эта публикация является любительским переводом статьи «How to Redirect in Vue» от Майкла Тиссена.

На обычных страницах мы можем ссылаться на другие страницы с помощью указания ссылки через a-тег:

<a href="www.google.com">Google</a>

Если вы используете Vue Router, вы будете использовать специальный компонент RouterLink:

<RouterLink :to="www.google.com">Google</RouterLink>

Но что делать, если вы хотите сделать редирект программно, без использования тега для ссылки?

Если вы используете Vue Router, то вы просто подставите ссылку в компонент роутера, в метод push:

this.$router.push('www.yoursite.com/blog')

В этой заметке мы рассмотрим все способы редиректа на внутренние и внешние страницы. Разберём случаи использования Vue с Vue Router и без него.

Читать далее

26 советов для Vue, которые сэкономят ваше время

Эта публикация является любительским переводом статьи «26 Time Saving Tips for Vue» от Майкла Тиссена.

Никто не любит тратить своё время зря. При написании приложений на Vue вы можете потратить лишнее время на неправильные подходы. Новичку, а порой и опытному разработчику, не всегда известно, что станет камнем преткновения. В этой статье вы узнаете 26 полезных советов о разработке Vue-приложений, которые сэкономят ваше время и нервы!

Читать далее

Как в Blade вывести переменную Vue

Иногда возникает необходимость написать что-то вроде {{ $route.name }} внутри blade-шаблона в Laravel-приложении. Laravel тут же поругается, что такой переменной нет. Выход такой:

<div class="custom-block">
    @{{ $route.name }}
    ...
</div>

Используйте синтаксис @{{ }} для такой задачи.

Наложение части (1-2px) предыдущего элемента в текущий слайд в Owl-carousel

При вёрстке возникла ситуация, когда в owl-carousel показывалась полоска в 1−2 пикселя от предыдущего слайда, при этом owl-stage имел свойство display: flex, чтобы высота всех элементов была 100%.

Справа видно вылезающий блок со следующим изображением

Исправляется всё просто — надо убрать внутри owl-stage псевдо-элемент :after:

.owl-carousel .owl-stage:after {
  content: none;
}

Обсуждение этой проблемы есть и в официальном репозитории owl-carousel.

Ошибка в регулярных выражениях роутов Yii2 в PHP7.3

Недавно занимался отладкой старого приложения на Yii2 и встретил странную ошибку:

yiibaseErrorException: preg_match(): Compilation failed: invalid range in character class at offset 376 in .../vendor/yiisoft/yii2/web/UrlRule.php

Понятно: что-то не так с роутами в конфигах. Отследил, с какими конкретно роутами проблема, и вот что оказалось:

preg_match('/[w-.]+/', ''); // Этот код не будет работать в PHP7.3
preg_match('/[w\-.]+/', ''); // Это работает в PHP7.3, необходимо экранировать дефис

Дело в том, что в PHP7.3 обновился движок регулярных выражений с PCRE на PCRE2. Вот выдержка из списка обновлений PHP7.3:

With PCRE2_EXTRA_BAD_ESCAPE_IS_LITERAL set, escape sequences such as \s which are valid in character classes, but not as the end of ranges, were being treated as literals. An example is [_-\s] (but not [\s-_] because that gave an error at the start of a range). Now an «invalid range» error is given independently of PCRE2_EXTRA_BAD_ESCAPE_IS_LITERAL.

Почему в выдаче гугла больше нет звёзд рейтинга? Микроразметка для статей

Раньше можно было добавить возможность голосования к статье, разбавить всё это микроразметкой, и гугл в поисковой выдаче показывал звёзды рейтинга в сниппете, что повышало количество кликов (CTR). Сейчас гугл обновил правила. По новым правилам, звёзды будут отображаться только у следующих типов материалов:

  • книга,
  • курс,
  • мероприятие,
  • игра,
  • инструкция «how-to»,
  • локальный бизнес,
  • фильм,
  • эпизод сериала,
  • музыкальный плейлист,
  • музыкальная запись,
  • рецепт,
  • продукт (товар),
  • приложение/компьютерная программа.

В списке нет статей, записей в блогах. У всех таких материалов звёзды пропали в выдаче, а инструмент проверки микроразметки от гугла выдаёт ошибку «Article/BlogPosting/Person — недопустимый тип целевого объекта для свойства itemReviewed».

Что делать с этим?

  1. Починить микроразметку;
  2. Следить за изменениями от Гугла. Вероятно, они в ближайшее время ещё доработают свой механизм.

Важно! Не стоит пытаться обмануть Гугл, манипулируя микроразметкой. За это могут прилететь ручные меры — об этом ниже.

Почему у некоторых конкурентов есть звёзды в статьях?

Некоторые сайты некорректно размечают часть статейных страниц, помечая их как Product. Внутри Product размечают рейтинг, а Гугл думает что всё хорошо и показывает звёзды.

Особенно сильно этим выделяются сайты, которые используют виджет комментариев Cackle. Этот виджет выводит комментарии с микроразметкой звёзд, но при этом говорит, что на странице Product, а не Article.

По правилам Гугла это является нарушением. На форуме тоже об этом написали. Кратко: за такое нарушение прилетают ручные меры. Скорее всего, ваши конкуренты либо получат ручные меры, либо просто лишатся звёзд в ближайшее время. Если же всё останется как есть, то скоро на всех сайта будут виджеты Cackle, а тогда звёзды будут у всех или ни у кого :-).

Как скрыть шаблон Vue.js пока он не отрендерится. Директива [v-cloak]

Бывают ситуации, когда вы загружаете страницу с каким-то компонентом Vue, и при этом страница при загрузке «прыгает»: сначала показывает синтаксис шаблона Vue, а потом рендерит сам шаблон. Вот пример:

Страница загрузилась, а компонент Vue ещё не отрендерился
Читать далее