Questions d'entretiens chez 8 Worst Vue.js

Bonjour, Habr!

Aimez-vous les entretiens d'embauche? Et souvent les dépenser? Si la réponse à la deuxième question est «Oui», alors parmi les candidats, vous avez probablement rencontré des personnes excellentes et intelligentes qui ont répondu à toutes vos questions et ont approché la fin de la paie.

Mais vous, bien sûr, ne voulez pas trop payer les professionnels. Et il est vital de paraître plus intelligent qu'eux, ne les laissez que pour la durée de l'entretien.

Si vous avez des problèmes avec cela, alors bienvenue au chat. Vous y trouverez les questions les plus délicates et les plus perverses sur Vue, ce qui mettra tout candidat en place et vous fera douter de vos compétences professionnelles.

image

1. Déclencheur Watcher à l'intérieur des crochets du cycle de vie


Cette question peut sembler facile, mais je vous garantis que personne, même le développeur le plus avancé, n'y répondra. Vous pouvez lui demander au début de l'entretien pour que le candidat ressent immédiatement votre supériorité.

Question:

Il existe un composant TestComponent qui a une variable de montant. À l'intérieur des principaux crochets du cycle de vie, nous le définissons sur une valeur dans l'ordre numérique de 1 à 6. Watcher, qui affiche sa valeur dans la console, se tient sur cette variable.

Nous créons une instance TestComponent et la supprimons après quelques secondes. Il faut dire que nous verrons dans la sortie console.

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> 

Je vais vous donner un indice: "2345" est la mauvaise réponse.

La réponse
Dans la console, nous ne verrons que le chiffre 4.

Explication
L'instance elle-même n'a pas encore été créée dans le hook beforeCreate, watcher ne fonctionnera pas ici.

Watcher déclenche des modifications sur les crochets créés, beforeMount et montés. Étant donné que tous ces crochets sont appelés pendant un tick, Vue appellera watcher une fois à la fin, avec une valeur de 4.

Vue se désabonnera de la surveillance du changement de la variable avant d'appeler les hooks beforeDestroy et destroy, donc 5 et 6 n'atteindront pas la console.

Sandbox avec un exemple pour vous assurer que la réponse

2. Comportement implicite des accessoires


Cette question est basée sur le comportement des accessoires rares dans Vue. Tous les programmeurs, bien sûr, exposent simplement les validations nécessaires pour prop'ov et ne rencontrent jamais un tel comportement. Mais le candidat n'a pas besoin de le dire. Il serait préférable de poser cette question, d'y jeter un coup d'œil condamnant après une réponse incorrecte et de passer à la suivante.

Question:

En quoi l'hélice avec un type booléen diffère-t-elle des autres?

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

La réponse
Prop avec un type booléen diffère de tous les autres en ce que Vue a un casting de type spécial pour cela.

Si une chaîne vide ou le nom de l'accessoire lui-même dans kebab-case est passé en paramètre, Vue le convertira en vrai.

Un exemple:

Nous avons un fichier avec prop booléen:

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

Tous les cas d'utilisation valides pour le composant TestComponent sont indiqués ci-dessous.

 /* 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 avec un exemple pour vous assurer que la réponse

3. Utilisation d'un tableau dans $ refs


Si votre candidat sait comment fonctionne le cadre de l'intérieur vers le niveau Evan Yu, vous avez encore quelques atouts dans votre manche: vous pouvez poser une question sur le comportement non documenté et non évident du cadre.

Question:

Vuex contient un tableau d'objets de fichiers, chacun des objets du tableau a des propriétés de nom et id uniques. Ce tableau est mis à jour toutes les quelques secondes, des éléments sont supprimés et ajoutés.

Nous avons un composant qui affiche le nom de chaque objet tableau avec un bouton, en cliquant sur lequel l'élément dom associé au fichier courant doit être affiché dans la 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> 

Il faut dire où est l'erreur potentielle et comment y remédier.

La réponse
Le problème est que le tableau à l'intérieur de $ refs peut ne pas aller dans le même ordre que le tableau d'origine ( lien vers le problème ). Autrement dit, une telle situation peut se produire: nous cliquons sur le bouton du troisième élément de la liste, et l'élément dom du second est affiché sur la console.

Cela se produit uniquement lorsque les données du tableau changent fréquemment.

Les méthodes de la solution sont écrites sur GitHub:

1. Créez une référence unique pour chaque article

 <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. Attribut supplémentaire

 <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. Loisirs de composants étranges


Question:

Nous avons un composant spécial qui écrit sur la console chaque fois que le crochet monté est appelé:

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


Ce composant est utilisé dans le composant TestComponent. Il a un bouton, en appuyant sur lequel pendant 1 seconde le message Top message apparaîtra.
 /* 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> 

Cliquez sur le bouton et voyez ce qui se passe dans la console:



La première monture était attendue, mais où sont les deux autres? Comment y remédier?

Sandbox avec un exemple pour comprendre l'erreur et la corriger

La réponse
Le problème vient ici des particularités de la recherche de différences de DOM virtuels dans Vue.

Au tout début, notre DOM virtuel ressemble à ceci:


Après avoir cliqué sur le bouton, cela ressemble à ceci:



Vue essaie de faire correspondre l'ancien DOM virtuel avec le nouveau pour comprendre ce qui doit être supprimé et ajouté:


Les éléments supprimés sont barrés en rouge, les éléments créés sont surlignés en vert

Vue n'a pas pu trouver le composant TestMount, il l'a donc recréé.

Une situation similaire se répétera une seconde après avoir appuyé sur le bouton. À ce stade, le composant TestMounted affiche des informations sur sa création sur la console pour la troisième fois.

Pour résoudre le problème, placez simplement l'attribut key sur le div avec le composant TestMounted:

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

Maintenant, Vue pourra cartographier sans ambiguïté les éléments nécessaires des DOM virtuels.

5. Création d'un composant de table


Défi:

Il est nécessaire de créer un composant qui prend un tableau avec des données et les affiche dans un tableau. Il est nécessaire de donner la possibilité de spécifier les colonnes et le type de cellule.

Les informations sur les colonnes et le type de cellule doivent être transmises via un composant spécial (identique à 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> 

Au début, la tâche ne contenait pas la nécessité de faire la même chose que element-ui. Mais il s'est avéré que certaines personnes sont en mesure de terminer la tâche dans le libellé d'origine. Par conséquent, l'exigence a été ajoutée pour transmettre des informations sur les colonnes et le type de cellule utilisant des composants.

Je suis sûr que vos interlocuteurs seront tout le temps dans la stupeur. Vous pouvez leur donner 30 minutes pour résoudre un tel problème.

Solution
L'idée principale est de transférer toutes les données vers le composant CustomTable dans le composant CustomColumn, puis il restituera tout lui-même.

Voici un exemple d'implémentation. Il ne prend pas en compte certains points (comme le changement d'étiquette), mais le principe de base doit être clair.

 /* 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. Création d'un portail


Si votre candidat n'a pas terminé la tâche précédente, il n'y a rien à craindre: vous pouvez lui en donner une de plus, pas moins difficile!

Défi:

Créez un composant Portal et PortalTarget, comme la bibliothèque portal-vue :

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

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

Solution
Pour créer un portail, vous devez implémenter trois objets:

  • Entrepôt de données du portail
  • Composant de portail qui ajoute des données au magasin
  • Le composant PortalTarget qui récupère les données du magasin et les affiche

 /* 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); }, }, }; 

Cette solution ne prend pas en charge la modification de l'attribut to, ne prend pas en charge les animations pendant la transition et ne prend pas en charge les valeurs par défaut, comme portal-vue. Mais l'idée générale doit être claire.

7. Prévention de la réactivité


Question:

Vous avez reçu un grand objet de l'API et l'avez affiché à l'utilisateur. Quelque chose comme ça:

 /* 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> 

Il y a un problème dans ce code. Nous ne modifions pas le nom, le prix, la qualité et les autres propriétés de l'objet article. Mais Vue ne le sait pas et ajoute de la réactivité à chaque champ.

Comment éviter cela?

La réponse
Pour éviter de changer les propriétés en réactif, vous devez figer l'objet avant de l'ajouter dans Vue à l'aide de la méthode Object.freeze.

Vue vérifiera si l'objet est gelé à l'aide de la méthode Object.isFrozen. Et si c'est le cas, Vue n'ajoutera pas de getters et setters réactifs aux propriétés de l'objet, car ils ne peuvent en aucun cas être modifiés. Avec de très gros objets, cette optimisation permet d'économiser jusqu'à plusieurs dizaines de millisecondes.

Le composant optimisé ressemblera à ceci:

 /* 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 fige uniquement les propriétés de l'objet lui-même. Ainsi, si un objet contient des objets imbriqués, ils doivent également être gelés.

Mise à jour du 19 janvier 2019 : Sur les conseils de Dmitry Zlygin, j'ai regardé la bibliothèque vue- nonreactive et j'ai trouvé un autre moyen. Il est parfait pour les situations où vous avez beaucoup d'objets imbriqués.

Vue n'ajoutera pas de réactivité à un objet s'il voit qu'il est déjà réactif. Nous pouvons tromper Vue en créant un observateur vide pour l'objet:

 /* 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. Erreurs des appareils lents


Question:

Il existe un composant avec une méthode qui affiche l'une des propriétés de l'objet élément dans la console, puis supprime l'objet élément:

 /* 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> 

Qu'est-ce qui pourrait mal tourner ici?

La réponse
Le problème est qu'après le premier clic sur le bouton Vue, il faut un certain temps pour mettre à jour le DOM pour l'utilisateur et supprimer le bouton. Par conséquent, l'utilisateur peut parfois double-cliquer. La méthode logAndClean fonctionne normalement la première fois et se bloque une deuxième fois, car elle ne peut pas obtenir la propriété value.

Je vois constamment un tel problème dans le traqueur d'erreurs, en particulier souvent sur les téléphones portables bon marché pour 4-5k roubles.

Pour l'éviter, il suffit d'ajouter une vérification de l'existence de l'élément au début de la fonction:

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

Pour reproduire le bug, vous pouvez vous rendre dans le bac à sable avec un exemple, définir la limitation maximale du CPU et cliquer rapidement sur le bouton. Par exemple, je l'ai fait.



Lien Sandbox pour vous assurer que la réponse

Merci d'avoir lu l'article jusqu'au bout! Je pense que maintenant vous pouvez certainement paraître plus intelligent lors des entretiens et les salaires de vos candidats vont baisser de manière significative!

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


All Articles