8 худших вопросов на собеседовании по Vue.js
Привет, Хабр!
Вы любите собеседования? И часто проводите их? Если ответ на второй вопрос «Да», то среди кандидатов вам наверняка встречались отличные и умные люди, которые отвечали на все ваши вопросы и приближались к концу зарплатной вилки.
Но вы, конечно, не хотите платить профессионалам слишком много. И жизненно необходимо казаться умнее них, пускай только на время собеседования.
Если у вас с этим проблемы, то добро пожаловать по кат. Там вы найдете самые каверзные и извращенные вопросы по Vue, которые поставят любого кандидата на место и заставят сомневаться в своих профессиональных навыках.
1. Триггер watcher’ов внутри хуков жизненного цикла
Этот вопрос может показаться легким, но я гарантирую, на него не ответит ни один, даже самый прошареный разработчик. Можете задать его в начале собеседования, чтобы кандидат сразу почувствовал ваше превосходство.
Вопрос:
Есть компонент TestComponent, у которого есть переменная amount. Внутри основных хуков жизненного цикла мы задаем ей значение в числовом порядке от 1 до 6. На эту переменную стоит watcher, который выводит ее значение в консоль.
Мы создаем инстанс TestComponent и через несколько секунд удаляем. Необходимо сказать, что мы увидим в выводе консоли.
Код:
/* TestComponent.vue */
I'm Test component
Дам подсказку:»2345» — неправильный ответ.
В консоли мы увидим только цифру 4.
Watcher срабатывает на изменения в хуке created, beforeMount и mounted. Так как все эти хуки вызываются во время одного тика, Vue вызовет watcher один раз в самом конце, со значением 4.
Vue отпишется от наблюдения за изменением переменной перед вызовом хуков beforeDestroy и destroyed, поэтому 5 и 6 не попадут в консоль.
Песочница с примером, чтобы убедиться в ответе
2. Неявное поведение props
Этот вопрос основан на редком поведении props во Vue. Все программисты, конечно, просто выставляют нужные валидации для prop’ов и никогда не сталкиваются с таким поведением. Но этого говорить кандидату не нужно. Лучше будет задать этот вопрос, бросить на него осуждающий взгляд после неправильного ответа и перейти к следующему.
Вопрос:
Чем поведение prop’а с типом Boolean отличается от остальных?
/* SomeComponent.vue */
/* ... */
Если в качестве параметра будет передана пустая строка или название самого prop’а в kebab-case, то Vue преобразует это в true.
Пример:
У нас есть файл с Boolean prop’ом:
/* TestComponent.vue */
I'm TestComponent
Ниже показаны все валидные варианты использования компонента TestComponent.
/* TestWrapper.vue */
Песочница с примером, чтобы убедиться в ответе
3. Использование массива в $refs
Если ваш кандидат знает как работает фреймворк изнутри на уровне Эвана Ю, у вас все еще есть несколько козырей в рукаве: вы можете задать вопрос о незадокументированном и неочевидном поведении фреймворка.
Вопрос:
Во Vuex лежит массив объектов files, у каждого из объектов в массиве есть уникальные свойства name и id. Этот массив раз в несколько секунд обновляется, в нем удаляются и добавляются элементы.
У нас есть компонент, который выводит name каждого объекта массива с кнопкой, по клику на которую в консоль должен выводиться dom-элемент, связанный с текущим файлом:
/* FileList.vue */
{{ file.name }}
Необходимо сказать, где здесь потенциальная ошибка и как ее исправить.
Такое происходит только тогда, когда в массиве часто изменяются данные.
Методы решения написаны в issue на GitHub’е:
1. Создавать уникальный ref для каждого элемента
{{ file.name }}
2. Дополнительный аттрибут
{{ file.name }}
4. Странное пересоздание компонента
Вопрос:
У нас есть специальный компонент, который пишет в консоль каждый раз, когда вызывается хук mounted:
/* TestMount.vue */
I'm TestMount
Этот компонент используется в компоненте TestComponent. Он имеет кнопку, по нажатию на которую на 1 секунду покажется надпись Top message.
/* TestComponent.vue */
Top message
Кликнем на кнопку и посмотрим, что будет в консоли:
Первый маунт был ожидаемый, но откуда еще два? Как это исправить?
Песочница с примером, чтобы понять ошибку и исправить ее
В самом начале наш Virtual DOM выглядит так:
После клика на кнопку он выглядит так:
Vue пытается сопоставить старый Virtual DOM с новым, чтобы понять, что нужно удалить и добавить:
Удаленные элементы перечеркнуты красным, созданные — выделены зеленым
Vue не смог найти компонент TestMount, поэтому пересоздал его.
Аналогичная ситуация повторится через секунду после нажатия кнопки. В этот момент компонент TestMounted третий раз выведет на консоль информацию о своем создании.
Чтобы пофиксить проблему, достаточно поставить атрибут key к div’у с компонентом TestMounted:
/* TestComponent.vue */
/* ... */
Теперь Vue сможет однозначно сопоставить нужные элементы Virtual DOM’ов.
5. Создание компонента-таблицы
Задача:
Необходимо создать компонент, который принимает массив с данными и выводит их в виде таблицы. Необходимо давать возможность задавать колонки и вид ячейки.
Информация о колонках и виде ячейки должна передаваться через специальный компонент (так же, как и у element-ui):
/* SomeComponent.vue */
{{ item.name }}
{{ item.id }}
В начале задача не содержала необходимости делать так же, как и у element-ui. Но оказалось, что некоторые люди способны выполнить задачу в первоначальной формулировке. Поэтому и добавилось требование передавать информацию о колонках и виде ячейки с помощью компонентов.
Уверен, ваши собеседуемые будут все время в ступоре. Можете дать им 30 минут на решение такой задачи.
Ниже дан пример реализации. Он не учитывает некоторых моментов (как, например, изменение 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 => (
{columnData.label}
))}
// Создаем строки таблицы
{items.map(item => (
{columnsData.map(columnData => (
{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 */
Super header
/* 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 }}
{{ item.price }}
{{ item.quality }}
В этом коде есть проблема. У объекта item мы не меняем name, price, quality и остальные свойства. Но Vue об этом не знает и добавляет реактивность в каждое поле.
Как можно этого избежать?
Vue проверит, заморожен ли объект с помощью метода Object.isFrozen. И если это так, то Vue не станет добавлять реактивные геттеры и сеттеры к свойствам объекта, так как их в любом случае невозможно изменить. При очень больших объектах эта оптимизация помогает сохранить до нескольких десятков миллисекунд.
Оптимизированный компонент будет выглядеть так:
/* ItemView.vue */
Object.freeze замораживает только свойства самого объекта. Так что, если объект содержит в себе вложенные объекты, их тоже необходимо заморозить.
8. Ошибки медленных девайсов
Вопрос:
Есть компонент с методом, который выводит одно из свойств объекта item в консоль, а затем удаляет объект item:
/* SomeComponent.vue */
Что здесь может пойти не так?
Я постоянно вижу такую проблему в трекере ошибок, особенно часто на дешевых мобильниках за 4–5к рублей.
Чтобы избежать ее, просто добавьте проверку на существование item в начале функции:
Чтобы воспроизвести баг, можете перейти в песочницу с примером, выставить максимальный троттлинг CPU и быстро-быстро покликать на кнопку. У меня, например, получилось.
Ссылка на песочницу, чтобы убедиться в ответе
Спасибо, что дочитали статью до конца! Думаю, теперь вы точно сможете казаться умнее на собеседованиях и у ваших кандидатов сильно упадут зарплатные ожидания!