📜 ⬆️ ⬇️

8 worst questions on Vue.js interview

Hi, Habr!

Do you like job interviews? And often spend them? If the answer to the second question is "Yes", then among the candidates you probably met excellent and intelligent people who answered all your questions and were nearing the end of the salary fork.

But you certainly don’t want to pay professionals too much. And it is vital to seem smarter than them, let them only for the duration of the interview.
')
If you have problems with this, then welcome under cat. There you will find the most tricky and perverted questions on Vue, which will put any candidate in their place and make them doubt their professional skills.

image

1. Watcher trigger inside the life cycle hooks


This question may seem easy, but I guarantee that no one, even the most advanced developer, will answer it. You can ask him at the beginning of the interview so that the candidate will immediately feel your superiority.

Question:

There is a component TestComponent, which has a variable amount. Inside the main hooks of the life cycle, we give it a value in numerical order from 1 to 6. This variable is watcher, which displays its value in the console.

We create the TestComponent instance and delete it in a few seconds. It must be said that we will see in the console output.

Code:

/* TestComponent.vue */ <template> <span> I'm Test component </span> </template> <script> export default { data() { return { amount: 0, }; }, watch: { amount(newVal) { console.log(newVal); }, }, beforeCreate() { this.amount = 1; }, created() { this.amount = 2; }, beforeMount() { this.amount = 3; }, mounted() { this.amount = 4; }, beforeDestroy() { this.amount = 5; }, destroyed() { this.amount = 6; }, }; </script> 

I'll give you a hint: “2345” is the wrong answer.

Answer
In the console, we see only the number 4.

Explanation
In the beforeCreate hook, the instance itself has not yet been created, the watcher will not work here.

Watcher is triggered by changes in the created, beforeMount and mounted hooks. Since all these hooks are called during one tick, Vue will call the watcher once at the very end, with a value of 4.

Vue will unsubscribe from observing the change in the variable before calling the hooks beforeDestroy and destroyed, so 5 and 6 will not get into the console.

Sandbox with an example to make sure you answer

2. Implicit props behavior


This question is based on the rare props behavior in Vue. All programmers, of course, simply put the necessary validations for propes and never encounter such behavior. But the candidate does not need to say this. It would be better to ask this question, cast a disapproving glance at it after the wrong answer and go on to the next one.

Question:

How does a prop with a Boolean type differ from the rest?

 /* SomeComponent.vue */ <template> <!-- ... --> </template> <script> export default { /* ... */ props: { testProperty: { type: Boolean, }, }, }; </script> 

Answer
A prop with a Boolean type is different from all the others in that there is a special type cast for it in Vue.

If the parameter is the empty string or the name of the prop itself in kebab-case, then Vue will convert this to true.

Example:

We have a file with Boolean prop:

 /* TestComponent.vue */ <template> <div v-if="canShow"> I'm TestComponent </div> </template> <script> export default { props: { canShow: { type: Boolean, required: true, }, }, }; </script> 

The following are all valid use cases for the TestComponent component.

 /* TestWrapper.vue */ <template> <div> <!--    canShow   true  TestComponent --> <TestComponent canShow="" /> <!--    , vue-template-compiler      prop' --> <TestComponent canShow /> <!--  canShow   true --> <TestComponent canShow="can-show" /> </div> </template> <script> import TestComponent from 'path/to/TestComponent'; export default { components: { TestComponent, }, }; </script> 


Sandbox with an example to make sure you answer

3. Using an array in $ refs


If your candidate knows how the framework works from the inside at the level of Evan Yu, you still have a few trump cards up your sleeve: you can ask a question about the undocumented and unobvious behavior of the framework.

Question:

In Vuex, there is an array of files objects, each of the objects in the array has unique name and id properties. This array is updated every few seconds, and elements are removed and added to it.

We have a component that displays the name of each object in the array with a button, on clicking on which the dom element associated with the current file should be output to the console:

 /* FileList.vue */ <template> <div> <div v-for="(file, idx) in files" :key="file.id" ref="files" > {{ file.name }} <button @click="logDOMElement(idx)"> Log DOM element </button> </div> </div> </template> <script> import { mapState } from 'vuex'; export default { computed: { ...mapState('files'), }, methods: { logDOMElement(idx) { console.log(this.$refs.files[idx]); }, }, }; </script> 

It is necessary to say where the potential error is and how to correct it.

Answer
The problem is that the array inside $ refs may not go in the same order as the original array ( link to issue ). That is, this situation may occur: click on the button of the third element of the list, and the dom-element of the second is displayed on the console.

This happens only when the data in the array changes frequently.

Solution methods are written in an issue on GitHub:

1. Create a unique ref for each item.

 <template> <div> <div v-for="(file, idx) in files" :key="file.id" :ref="`file_${idx}`" > {{ file.name }} <button @click="logDOMElement(idx)"> Log DOM element </button> </div> </div> </template> <script> import { mapState } from 'vuex'; export default { computed: { ...mapState('files'), }, methods: { logDOMElement(idx) { console.log(this.$refs[`file_{idx}`]); }, }, }; </script> 

2. Additional attribute

 <template> <div> <div v-for="(file, idx) in files" :key="file.id" :data-file-idx="idx" > {{ file.name }} <button @click="logDOMElement(idx)"> Log DOM element </button> </div> </div> </template> <script> import { mapState } from 'vuex'; export default { computed: { ...mapState('files'), }, methods: { logDOMElement(idx) { const fileEl = this.$el.querySelector(`*[data-file-idx=${idx}]`); console.log(fileEl); }, }, }; </script> 


4. Strange re-creation of the component


Question:

We have a special component that writes to the console every time the hook hook is called:

 /* TestMount.vue */ <template> <div> I'm TestMount </div> </template> <script> export default { mounted() { console.log('TestMount mounted'); }, }; </script> 


This component is used in the TestComponent component. It has a button, by clicking on which the message Top message appears for 1 second.
 /* TestComponent.vue */ <template> <div> <div v-if="canShowTopMessage"> Top message </div> <div> <TestMount /> </div> <button @click="showTopMessage()" v-if="!canShowTopMessage" > Show top message </button> </div> </template> <script> import TestMount from './TestMount'; export default { components: { TestMount, }, data() { return { canShowTopMessage: false, }; }, methods: { showTopMessage() { this.canShowTopMessage = true; setTimeout(() => { this.canShowTopMessage = false; }, 1000); }, }, }; </script> 

Click on the button and see what will be in the console:



The first mount was expected, but from where two more? How to fix it?

Sandbox with an example to understand the error and correct it.

Answer
The problem here arises due to the peculiarities of finding the differences of Virtual DOMs in Vue.

At the very beginning, our Virtual DOM looks like this:


After clicking on the button, it looks like this:



Vue tries to match the old Virtual DOM with the new one in order to understand what needs to be removed and added:


Deleted items are crossed out in red, created - highlighted in green

Vue could not find the TestMount component, so I re-created it.

A similar situation will repeat a second after pressing the button. At this point, the TestMounted component for the third time will display information about its creation to the console.

To fix the problem, just put the key attribute on the div with the TestMounted component:

 /* TestComponent.vue */ <template> <div> <!-- ... --> <div key="container"> <TestMount /> </div> <!-- ... --> </div> </template> /* ... */ 

Now Vue can unambiguously match the necessary elements of Virtual DOMs.

5. Creating a table component


Task:

You need to create a component that takes an array of data and displays them in a table. It is necessary to give the opportunity to set the column and cell type

Information about the columns and cell type should be transmitted through a special component (as well as element-ui ):

 /* SomeComponent.vue */ <template> <CustomTable :items="items"> <CustomColumn label="Name"> <template slot-scope="item"> {{ item.name }} </template> </CustomColumn> <CustomColumn label="Element Id"> <template slot-scope="item"> {{ item.id }} </template> </CustomColumn> </CustomTable> </template> 

In the beginning, the task did not contain the need to do the same as with element-ui. But it turned out that some people are able to complete the task in the original formulation. Therefore, the requirement was added to transfer information about the columns and cell type using components.

I am sure that your interviewees will be in a stupor all the time. You can give them 30 minutes to solve this problem.

Decision
The basic idea is to transfer all the data to the CustomTable component in the CustomColumn component, and then it will already show everything.

Below is an example implementation. It does not take into account certain points (such as changing the label), but the basic principle should be clear.

 /* 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 ( <div> //   CustomColumn {defaultSlot} <table> //   <tr> {columnsData.map(columnData => ( <td key={columnData.label}> {columnData.label} </td> ))} </tr> //    {items.map(item => ( <tr> {columnsData.map(columnData => ( <td key={columnData.label}> {columnData.createCell(item)} </td> ))} </tr> ))} </table> </div> ); }, props: { items: { type: Array, required: true, }, }, data() { return { columnsData: [], }; }, methods: { setColumnData(columnData) { this.columnsData.push(columnData); }, }, }; 


6. Creating a portal


If your candidate did not cope with the previous task, do not worry: you can give him one more, no less difficult!

Task:

Create the Portal and PortalTarget components as in the portal-vue library:

 /* FirstComponent.vue */ <template> <div> <Portal to="title"> Super header </Portal> </div> </template> 

 /* SecondComponent.vue */ <template> <div> <PortalTarget name="title" /> </div> </template> 

Decision
To create a portal, you need to implement three objects:

  • Portal Data Store
  • Portal component that adds data to the repository
  • The PortalTarget component, which retrieves data from the repository and displays it

 /* 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 ( <div class="portal-target"> {portalData.portalEl} </div> ); }, computed: { portalData() { return dataBus.getPortalData(this.name); }, }, }; 

This solution does not support changing the to attribute, does not support animation through transition, and does not support default values, like portal-vue. But the general idea should be clear.

7. Prevent reactivity creation


Question:

You received a large object from api and displayed it to the user. Like that:

 /* ItemView.vue */ <template> <div v-if="item"> <div> {{ item.name }} </div> <div> {{ item.price }} </div> <div> {{ item.quality }} </div> <!--     --> </div> </template> <script> import getItemFromApi from 'path/to/getItemFromApi'; export default { data() { return { item: null, }; }, async mounted() { this.item = await getItemFromApi(); }, }; </script> 

There is a problem with this code. For the item object, we do not change the name, price, quality and other properties. But Vue does not know about this and adds reactivity to each field.

How can this be avoided?

Answer
To avoid changing properties to reactive, you need to freeze the object before adding inside Vue using the Object.freeze method.

Vue checks whether an object is frozen using the Object.isFrozen method. And if so, then Vue will not add reactive getters and setters to the properties of the object, since in any case it is impossible to change them. For very large objects, this optimization helps to save up to several tens of milliseconds.

The optimized component will look like this:

 /* ItemView.vue */ <template> <!-- ... --> </template> <script> import getItemFromApi from 'path/to/getItemFromApi'; export default { /* .... */ async mounted() { const item = await getItemFromApi(); Object.freeze(item); this.item = item; }, }; </script> 

Object.freeze freezes only the properties of the object itself. So, if an object contains embedded objects, they must also be frozen.

Update of 01/19/2019 : Following the advice of Dmitry Zlygin, he looked at the vue-nonreactive library and found another way. It is perfect for a situation where you have a lot of nested objects.

Vue will not add reactivity to an object if it sees that it is already reactive. We can trick Vue by creating an empty Observer for the object:

 /* ItemView.vue */ <template> <!-- ... --> </template> <script> import Vue from 'vue'; import getItemFromApi from 'path/to/getItemFromApi'; const Observer = new Vue() .$data .__ob__ .constructor; export default { /* .... */ async mounted() { const item = await getItemFromApi(); //   Observer   item.__ob__ = new Observer({}); this.item = item; }, }; </script> 


8. Errors of slow devices


Question:

There is a component with a method that outputs one of the properties of the item object to the console, and then deletes the item object:

 /* SomeComponent.vue */ <template> <div v-if="item"> <button @click="logAndClean()"> Log and clean </button> </div> </template> <script> export default { data() { return { item: { value: 124, }, }; }, methods: { logAndClean() { console.log(this.item.value); this.item = null; }, }, }; </script> 

What could go wrong here?

Answer
The problem is that after first clicking on the Vue button, it takes some time to update the DOM for the user and remove the button. Therefore, the user can sometimes double-click. The logAndClean method will work the first time normally, and the second time it will crash, because it cannot get the value property.

I constantly see such a problem in the error tracker, especially often on cheap mobile phones for 4-5k rubles.

To avoid it, simply add a check for the existence of an item at the beginning of the function:

 <template> <!-- ... --> </template> <script> export default { /* ... */ methods: { logAndClean() { const { item } = this; if (!item) { return; } console.log(item.value); this.item = null; }, }, }; </script> 

To reproduce the bug, you can go to the sandbox with an example, set the maximum CPU throttling and quickly click on the button. I, for example, succeeded.



Link to the sandbox to make sure you answer

Thank you for reading the article to the end! I think that now you can definitely seem smarter at the interviews and your candidates will drop their salary expectations!

Source: https://habr.com/ru/post/435700/


All Articles