8 худших вопросов на собеседовании по Vue.js

Привет, Хабр!

Вы любите собеседования? И часто проводите их? Если ответ на второй вопрос «Да», то среди кандидатов вам наверняка встречались отличные и умные люди, которые отвечали на все ваши вопросы и приближались к концу зарплатной вилки.

Но вы, конечно, не хотите платить профессионалам слишком много. И жизненно необходимо казаться умнее них, пускай только на время собеседования.

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

image

1. Триггер watcher’ов внутри хуков жизненного цикла


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

Вопрос:

Есть компонент TestComponent, у которого есть переменная amount. Внутри основных хуков жизненного цикла мы задаем ей значение в числовом порядке от 1 до 6. На эту переменную стоит watcher, который выводит ее значение в консоль.

Мы создаем инстанс TestComponent и через несколько секунд удаляем. Необходимо сказать, что мы увидим в выводе консоли.

Код:

/* TestComponent.vue */





Дам подсказку:»2345» — неправильный ответ.

Ответ

В консоли мы увидим только цифру 4.


Объяснение
В хуке beforeCreate еще не создан сам инстанс, watcher здесь работать не будет.

Watcher срабатывает на изменения в хуке created, beforeMount и mounted. Так как все эти хуки вызываются во время одного тика, Vue вызовет watcher один раз в самом конце, со значением 4.

Vue отпишется от наблюдения за изменением переменной перед вызовом хуков beforeDestroy и destroyed, поэтому 5 и 6 не попадут в консоль.


Песочница с примером, чтобы убедиться в ответе

2. Неявное поведение props


Этот вопрос основан на редком поведении props во Vue. Все программисты, конечно, просто выставляют нужные валидации для prop’ов и никогда не сталкиваются с таким поведением. Но этого говорить кандидату не нужно. Лучше будет задать этот вопрос, бросить на него осуждающий взгляд после неправильного ответа и перейти к следующему.

Вопрос:

Чем поведение prop’а с типом Boolean отличается от остальных?

/* SomeComponent.vue */





Ответ
Prop с типом Boolean отличается от всех остальных тем, что во Vue для него есть специальное приведение типов.

Если в качестве параметра будет передана пустая строка или название самого prop’а в kebab-case, то Vue преобразует это в true.

Пример:

У нас есть файл с Boolean prop’ом:

/* TestComponent.vue */





Ниже показаны все валидные варианты использования компонента TestComponent.

/* TestWrapper.vue */







Песочница с примером, чтобы убедиться в ответе

3. Использование массива в $refs


Если ваш кандидат знает как работает фреймворк изнутри на уровне Эвана Ю, у вас все еще есть несколько козырей в рукаве: вы можете задать вопрос о незадокументированном и неочевидном поведении фреймворка.

Вопрос:

Во Vuex лежит массив объектов files, у каждого из объектов в массиве есть уникальные свойства name и id. Этот массив раз в несколько секунд обновляется, в нем удаляются и добавляются элементы.

У нас есть компонент, который выводит name каждого объекта массива с кнопкой, по клику на которую в консоль должен выводиться dom-элемент, связанный с текущим файлом:

/* FileList.vue */





Необходимо сказать, где здесь потенциальная ошибка и как ее исправить.

Ответ
Проблема в том, что массив внутри $refs может идти не в том порядке, как и оригинальный массив (ссылка на issue). То есть, может произойти такая ситуация: кликаем на кнопку третьего элемента списка, а на консоль выводится dom-элемент второго.

Такое происходит только тогда, когда в массиве часто изменяются данные.

Методы решения написаны в issue на GitHub’е:

1. Создавать уникальный ref для каждого элемента





2. Дополнительный аттрибут






4. Странное пересоздание компонента


Вопрос:

У нас есть специальный компонент, который пишет в консоль каждый раз, когда вызывается хук mounted:

/* TestMount.vue */





Этот компонент используется в компоненте TestComponent. Он имеет кнопку, по нажатию на которую на 1 секунду покажется надпись Top message.

/* TestComponent.vue */





Кликнем на кнопку и посмотрим, что будет в консоли:

6i87irzaijkt4yqpqrncrxvvpvw.png

Первый маунт был ожидаемый, но откуда еще два? Как это исправить?

Песочница с примером, чтобы понять ошибку и исправить ее

Ответ
Проблема здесь возникает из-за особенностей поиска различий Virtual DOM’ов во Vue.

В самом начале наш Virtual DOM выглядит так:

num1hf9uxtgu3colqu98n6l7apc.png

После клика на кнопку он выглядит так:

lzi-jqtkskqsotfeduk7em0nc5u.png

Vue пытается сопоставить старый Virtual DOM с новым, чтобы понять, что нужно удалить и добавить:

c8uhilyiq6akagl4dwhmpilbdfo.png

Удаленные элементы перечеркнуты красным, созданные — выделены зеленым

Vue не смог найти компонент TestMount, поэтому пересоздал его.

Аналогичная ситуация повторится через секунду после нажатия кнопки. В этот момент компонент TestMounted третий раз выведет на консоль информацию о своем создании.

Чтобы пофиксить проблему, достаточно поставить атрибут key к div’у с компонентом TestMounted:

/* TestComponent.vue */



 /* ... */

Теперь Vue сможет однозначно сопоставить нужные элементы Virtual DOM’ов.

5. Создание компонента-таблицы


Задача:

Необходимо создать компонент, который принимает массив с данными и выводит их в виде таблицы. Необходимо давать возможность задавать колонки и вид ячейки.

Информация о колонках и виде ячейки должна передаваться через специальный компонент (так же, как и у element-ui):

/* SomeComponent.vue */



В начале задача не содержала необходимости делать так же, как и у element-ui. Но оказалось, что некоторые люди способны выполнить задачу в первоначальной формулировке. Поэтому и добавилось требование передавать информацию о колонках и виде ячейки с помощью компонентов.

Уверен, ваши собеседуемые будут все время в ступоре. Можете дать им 30 минут на решение такой задачи.

Решение
Основная идея состоит в том, чтобы в компоненте CustomColumn передать все данные компоненту CustomTable, а дальше он уже сам все срендерит.

Ниже дан пример реализации. Он не учитывает некоторых моментов (как, например, изменение label), но основной принцип должен быть понятен.

/* CustomColumn.js */

export default {
  render() {
    return null;
  },

  props: {
    label: {
      type: String,
      required: true,
    },
  },

  mounted() {
    // Передаем в компонент CustomTable необходимые данные
    this.$parent.setColumnData({
      label: this.label,
      createCell: this.$scopedSlots.default,
    });
  },
};

/* CustomTable.js */
/* Использется JSX, так как в template не получится использовать метод createCell, переданный из CustomColumn.js */

export default {
  render() {
    const { columnsData, items } = this;
    const { default: defaultSlot } = this.$slots;

    return (
      
// Создаем элементы CustomColumn {defaultSlot} // Создаем хедер {columnsData.map(columnData => ( ))} // Создаем строки таблицы {items.map(item => ( {columnsData.map(columnData => ( ))} ))}
{columnData.label}
{columnData.createCell(item)}
); }, props: { items: { type: Array, required: true, }, }, data() { return { columnsData: [], }; }, methods: { setColumnData(columnData) { this.columnsData.push(columnData); }, }, };

6. Создание портала


Если ваш кандидат не справился с предыдущим заданием, ничего страшного: можете дать ему еще одно, не менее сложное!

Задача:

Создать компонент Portal и PortalTarget, как у библиотеки portal-vue:

/* FirstComponent.vue */



/* SecondComponent.vue */



Решение
Для создания портала нужно реализовать три объекта:
  • Хранилище данных о порталах
  • Компонент Portal, который добавляет данные в хранилище
  • Компонент PortalTarget, который извлекает данные из хранилища и отображает их
/* dataBus.js */
/* Файл содержит реактивное хранилище данных */

import Vue from 'vue';

const bus = new Vue({
  data() {
    return {
      portalDatas: [],
    };
  },

  methods: {
    setPortalData(portalData) {
      const { portalDatas } = this;

      const portalDataIdx = portalDatas.findIndex(
        pd => pd.id === portalData.id,
      );

      if (portalDataIdx === -1) {
        portalDatas.push(portalData);

        return;
      }

      portalDatas.splice(portalDataIdx, 1, portalData);
    },

    removePortalData(portalDataId) {
      const { portalDatas } = this;

      const portalDataIdx = portalDatas.findIndex(
        pd => pd.id === portalDataId,
      );

      if (portalDataIdx === -1) {
        return;
      }

      portalDatas.splice(portalDataIdx, 1);
    },

    getPortalData(portalName) {
      const { portalDatas } = this;

      const portalData = portalDatas.find(pd => pd.to === portalName);

      return portalData || null;
    },
  },
});

export default bus;

/* Portal.vue */
/* Этот компонент передает данные в dataBus */

import dataBus from './dataBus';

let currentId = 0;

export default {
  props: {
    to: {
      type: String,
      required: true,
    },
  },

  computed: {
    // Уникальный id компонента.
    // Нужен для идентификации данных в dataBus
    id() {
      return currentId++;
    },
  },

  render() {
    return null;
  },

  created() {
    this.setPortalData();
  },
 
  // Подхватываем изменение слотов
  updated() {
    this.setPortalData();
  },

  methods: {
    setPortalData() {
      const { to, id } = this;
      const { default: portalEl } = this.$slots;

      dataBus.setPortalData({
        to,
        id,
        portalEl,
      });
    },
  },

  beforeDestroy() {
    dataBus.removePortalData(this.id);
  },
};

/* PortalTarget.vue */
/* Компонент извлекает и отображает данные */

import dataBus from './dataBus';

export default {
  props: {
    name: {
      type: String,
      required: true,
    },
  },

  render() {
    const { portalData } = this;

    if (!portalData) {
      return null;
    }

    return (
      
{portalData.portalEl}
); }, computed: { portalData() { return dataBus.getPortalData(this.name); }, }, };

Данное решение не поддерживает изменение атрибута to, не поддерживает анимации через transition и не имеет поддержки дефолтных значений, как portal-vue. Но общая идея должна быть понятна.

7. Предотвращение создания реактивности


Вопрос:

Вы получили от апи большой объект и отобразили его пользователю. Примерно так:

/* ItemView.vue */





В этом коде есть проблема. У объекта item мы не меняем name, price, quality и остальные свойства. Но Vue об этом не знает и добавляет реактивность в каждое поле.

Как можно этого избежать?

Ответ
Чтобы избежать изменения свойств на реактивные, надо заморозить объект перед добавлением внутрь Vue с помощью метода Object.freeze.

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

Оптимизированный компонент будет выглядеть так:

/* ItemView.vue */





Object.freeze замораживает только свойства самого объекта. Так что, если объект содержит в себе вложенные объекты, их тоже необходимо заморозить.

8. Ошибки медленных девайсов


Вопрос:

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

/* SomeComponent.vue */





Что здесь может пойти не так?

Ответ
Проблема в том, что после первого клика на кнопку Vue требуется некоторое время, чтобы обновить DOM для пользователя и убрать кнопку. Поэтому пользователь иногда может кликнуть два раза. Метод logAndClean отработает первый раз нормально, а второй раз крашнется, так как не сможет получить свойство value.

Я постоянно вижу такую проблему в трекере ошибок, особенно часто на дешевых мобильниках за 4–5к рублей.

Чтобы избежать ее, просто добавьте проверку на существование item в начале функции:





Чтобы воспроизвести баг, можете перейти в песочницу с примером, выставить максимальный троттлинг CPU и быстро-быстро покликать на кнопку. У меня, например, получилось.

noxeb5ysmokpbubphdzhibvmalg.png


Ссылка на песочницу, чтобы убедиться в ответе

Спасибо, что дочитали статью до конца! Думаю, теперь вы точно сможете казаться умнее на собеседованиях и у ваших кандидатов сильно упадут зарплатные ожидания!

© Habrahabr.ru