Hallo Habr!
Magst du Vorstellungsgespräche? Und oft ausgeben? Wenn die Antwort auf die zweite Frage „Ja“ lautet, haben Sie unter den Kandidaten wahrscheinlich hervorragende und kluge Leute getroffen, die alle Ihre Fragen beantwortet und sich dem Ende der Gehaltsabrechnung genähert haben.
Aber Sie wollen natürlich nicht zu viel für Profis bezahlen. Und es ist äußerst notwendig, klüger zu wirken als sie, lassen Sie sie nur für die Dauer des Interviews.
Wenn Sie Probleme damit haben, dann willkommen bei cat. Dort finden Sie die schwierigsten und perversesten Fragen zu Vue, die jeden Kandidaten einsetzen und Sie an Ihren beruflichen Fähigkeiten zweifeln lassen.

1. Watcher-Trigger in Lebenszyklus-Haken
Diese Frage mag einfach erscheinen, aber ich garantiere, dass nicht einer, selbst der fortschrittlichste Entwickler, sie beantworten wird. Sie können ihn zu Beginn des Interviews fragen, damit der Kandidat sofort Ihre Überlegenheit spürt.
Frage:Es gibt eine TestComponent-Komponente mit einer Betragsvariablen. Innerhalb der Haupt-Lifecycle-Hooks setzen wir einen Wert in numerischer Reihenfolge von 1 bis 6. Watcher, der seinen Wert in der Konsole anzeigt, steht auf dieser Variablen.
Wir erstellen eine TestComponent-Instanz und löschen sie nach einigen Sekunden. Es muss gesagt werden, dass wir in der Konsolenausgabe sehen werden.
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>
Ich werde einen Hinweis geben: "2345" ist die falsche Antwort.
Die AntwortIn der Konsole sehen wir nur die Nummer 4.
ErklärungDie Instanz selbst wurde noch nicht im beforeCreate-Hook erstellt. Watcher funktioniert hier nicht.
Watcher löst Änderungen an den erstellten, beforeMount- und gemounteten Hooks aus. Da alle diese Hooks während eines Ticks aufgerufen werden, ruft Vue ganz am Ende den Watcher mit einem Wert von 4 an.
Vue wird die Überwachung der Änderung der Variablen vor dem Aufruf von beforeDestroy und zerstörten Hooks abbestellen, sodass 5 und 6 nicht zur Konsole gelangen.
Sandbox mit einem Beispiel, um die Antwort sicherzustellen2. Implizites Requisitenverhalten
Diese Frage basiert auf dem seltenen Requisitenverhalten in Vue. Natürlich legen alle Programmierer einfach die notwendigen Validierungen für prop'ov offen und stoßen niemals auf ein solches Verhalten. Der Kandidat muss dies jedoch nicht sagen. Es wäre besser, diese Frage zu stellen, nach einer falschen Antwort einen verurteilenden Blick darauf zu werfen und mit der nächsten fortzufahren.
Frage:Wie unterscheidet sich Requisite mit einem Booleschen Typ von den anderen?
/* SomeComponent.vue */ <template> </template> <script> export default { props: { testProperty: { type: Boolean, }, }, }; </script>
Die AntwortProp mit einem Booleschen Typ unterscheidet sich von allen anderen darin, dass Vue einen speziellen Typ hat.
Wenn eine leere Zeichenfolge oder der Name der Requisite selbst im Kebab-Fall als Parameter übergeben wird, konvertiert Vue dies in true.
Ein Beispiel:
Wir haben eine Datei mit Boolescher Requisite:
/* TestComponent.vue */ <template> <div v-if="canShow"> I'm TestComponent </div> </template> <script> export default { props: { canShow: { type: Boolean, required: true, }, }, }; </script>
Alle gültigen Anwendungsfälle für die TestComponent-Komponente werden unten angezeigt.
/* TestWrapper.vue */ <template> <div> <TestComponent canShow="" /> <TestComponent canShow /> <TestComponent canShow="can-show" /> </div> </template> <script> import TestComponent from 'path/to/TestComponent'; export default { components: { TestComponent, }, }; </script>
Sandbox mit einem Beispiel, um die Antwort sicherzustellen3. Verwenden eines Arrays in $ refs
Wenn Ihr Kandidat weiß, wie das Framework auf der Ebene von Evan Yu von innen nach außen funktioniert, haben Sie noch ein paar Trumpfkarten im Ärmel: Sie können eine Frage zum undokumentierten und nicht offensichtlichen Verhalten des Frameworks stellen.
Frage:Vuex enthält ein Array von Dateiobjekten. Jedes der Objekte im Array verfügt über eindeutige Namen- und ID-Eigenschaften. Dieses Array wird alle paar Sekunden aktualisiert, Elemente werden gelöscht und hinzugefügt.
Wir haben eine Komponente, die den Namen jedes Array-Objekts mit einer Schaltfläche anzeigt, indem Sie darauf klicken, welches dom-Element, das der aktuellen Datei zugeordnet ist, in der Konsole angezeigt werden soll:
/* 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>
Es muss gesagt werden, wo der potenzielle Fehler liegt und wie er behoben werden kann.
Die AntwortDas Problem ist, dass das Array in $ refs möglicherweise nicht in derselben Reihenfolge wie das ursprüngliche Array angezeigt wird (
Link zum Problem ). Das heißt, eine solche Situation kann auftreten: Wir klicken auf die Schaltfläche des dritten Elements der Liste, und das dom-Element des zweiten wird auf der Konsole angezeigt.
Dies geschieht nur, wenn sich die Daten im Array häufig ändern.
Lösungsmethoden sind in Problem auf GitHub geschrieben:
1. Erstellen Sie für jeden Artikel eine eindeutige Referenz
<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. Zusätzliches Attribut
<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. Seltsame Komponentenwiederherstellung
Frage:Wir haben eine spezielle Komponente, die jedes Mal, wenn der gemountete Hook aufgerufen wird, in die Konsole schreibt:
/* TestMount.vue */ <template> <div> I'm TestMount </div> </template> <script> export default { mounted() { console.log('TestMount mounted'); }, }; </script>
Diese Komponente wird in der TestComponent-Komponente verwendet. Es hat eine Taste, durch die 1 Sekunde lang die Meldung Top-Meldung angezeigt wird.
/* 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>
Klicken Sie auf die Schaltfläche und sehen Sie, was in der Konsole passiert:

Das erste Reittier wurde erwartet, aber wo sind die anderen beiden? Wie kann ich das beheben?
Sandbox mit einem Beispiel, um den Fehler zu verstehen und zu behebenDie AntwortDas Problem ergibt sich hier aus den Besonderheiten der Suche nach Unterschieden von virtuellen DOMs in Vue.
Am Anfang sieht unser virtuelles DOM folgendermaßen aus:
Nach dem Klicken auf die Schaltfläche sieht es so aus:
Vue versucht, das alte virtuelle DOM mit dem neuen abzugleichen, um herauszufinden, was entfernt und hinzugefügt werden muss:
Gelöschte Elemente werden rot durchgestrichen, erstellte Elemente werden grün hervorgehobenVue konnte die TestMount-Komponente nicht finden und hat sie daher neu erstellt.
Eine ähnliche Situation wird eine Sekunde nach dem Drücken der Taste wiederholt. Zu diesem Zeitpunkt zeigt die TestMounted-Komponente der Konsole zum dritten Mal Informationen zu ihrer Erstellung an.
Um das Problem zu beheben, fügen Sie einfach das Schlüsselattribut mit der TestMounted-Komponente in das div ein:
/* TestComponent.vue */ <template> <div> <div key="container"> <TestMount /> </div> </div> </template> /* ... */
Jetzt kann Vue die erforderlichen Elemente von virtuellen DOMs eindeutig zuordnen.
5. Erstellen einer Tabellenkomponente
Herausforderung:Es ist erforderlich, eine Komponente zu erstellen, die ein Array mit Daten aufnimmt und in einer Tabelle anzeigt. Es muss die Möglichkeit gegeben werden, die Spalten und den Zelltyp anzugeben.
Informationen über die Spalten und den Zelltyp müssen über eine spezielle Komponente übertragen werden (wie
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>
Zu Beginn enthielt die Aufgabe nicht die Notwendigkeit, dasselbe wie element-ui zu tun. Es stellte sich jedoch heraus, dass einige Personen die Aufgabe im ursprünglichen Wortlaut erledigen können. Daher wurde die Anforderung hinzugefügt, Informationen über die Spalten und den Zelltyp unter Verwendung von Komponenten zu übertragen.
Ich bin mir sicher, dass Ihre Befragten die ganze Zeit über verblüfft sein werden. Sie können ihnen 30 Minuten Zeit geben, um ein solches Problem zu lösen.
LösungDie Hauptidee besteht darin, alle Daten an die CustomTable-Komponente in der CustomColumn-Komponente zu übertragen und dann alles selbst zu rendern.
Das Folgende ist eine Beispielimplementierung. Einige Punkte (wie das Ändern des Etiketts) werden nicht berücksichtigt, aber das Grundprinzip sollte klar sein.
export default { render() { return null; }, props: { label: { type: String, required: true, }, }, mounted() {
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. Portal erstellen
Wenn Ihr Kandidat die vorherige Aufgabe nicht erledigt hat, besteht kein Grund zur Sorge: Sie können ihm eine weitere geben, nicht weniger schwierig!
Herausforderung:Erstellen Sie eine Portal- und PortalTarget-Komponente wie die
Portal- Vue
- Bibliothek:
/* FirstComponent.vue */ <template> <div> <Portal to="title"> Super header </Portal> </div> </template>
/* SecondComponent.vue */ <template> <div> <PortalTarget name="title" /> </div> </template>
LösungUm ein Portal zu erstellen, müssen Sie drei Objekte implementieren:
- Portal Data Warehouse
- Portalkomponente, die dem Speicher Daten hinzufügt
- Die PortalTarget-Komponente, die Daten aus dem Geschäft abruft und anzeigt
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;
import dataBus from './dataBus'; let currentId = 0; export default { props: { to: { type: String, required: true, }, }, computed: {
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); }, }, };
Diese Lösung unterstützt das Ändern des Attributs to nicht, unterstützt keine Animationen während des Übergangs und unterstützt keine Standardwerte wie portal-vue. Aber die allgemeine Idee sollte klar sein.
7. Verhinderung der Reaktivität
Frage:Sie haben ein großes Objekt von der API erhalten und es dem Benutzer angezeigt. Ungefähr so:
/* 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>
In diesem Code ist ein Problem aufgetreten. Wir ändern nicht den Namen, den Preis, die Qualität und andere Eigenschaften des Artikelobjekts. Aber Vue weiß nichts davon und fügt jedem Feld Reaktivität hinzu.
Wie kann dies vermieden werden?
Die AntwortUm zu vermeiden, dass die Eigenschaften in reaktiv geändert werden, müssen Sie das Objekt einfrieren, bevor Sie es mit der Object.freeze-Methode in Vue hinzufügen.
Vue prüft mithilfe der Object.isFrozen-Methode, ob das Objekt eingefroren ist. In diesem Fall fügt Vue den Eigenschaften des Objekts keine reaktiven Getter und Setter hinzu, da diese auf keinen Fall geändert werden können. Bei sehr großen Objekten hilft diese Optimierung, bis zu mehreren zehn Millisekunden einzusparen.
Die optimierte Komponente sieht folgendermaßen aus:
/* 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 friert nur die Eigenschaften des Objekts selbst ein. Wenn ein Objekt verschachtelte Objekte enthält, müssen diese ebenfalls eingefroren werden.
Update vom 19. Januar 2019 : Auf Anraten von
Dmitry Zlygin habe ich mir die
vue-nicht-reaktive Bibliothek angesehen und einen anderen Weg gefunden. Es ist perfekt für Situationen, in denen Sie viele verschachtelte Objekte haben.
Vue fügt einem Objekt keine Reaktivität hinzu, wenn es feststellt, dass es bereits reaktiv ist. Wir können Vue austricksen, indem wir einen leeren Beobachter für das Objekt erstellen:
/* 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(); </script>
8. Fehler langsamer Geräte
Frage:Es gibt eine Komponente mit einer Methode, die eine der Eigenschaften des Elementobjekts in der Konsole anzeigt und dann das Elementobjekt entfernt:
/* 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>
Was könnte hier schief gehen?
Die AntwortDas Problem ist, dass es nach dem ersten Klicken auf die Schaltfläche Vue einige Zeit dauert, das DOM für den Benutzer zu aktualisieren und die Schaltfläche zu entfernen. Daher kann der Benutzer manchmal doppelklicken. Die logAndClean-Methode funktioniert beim ersten Mal normal und stürzt ein zweites Mal ab, da die value-Eigenschaft nicht abgerufen werden kann.
Ich sehe ständig ein solches Problem im Fehler-Tracker, besonders oft auf billigen Handys für 4-5k Rubel.
Um dies zu vermeiden, fügen Sie am Anfang der Funktion einfach eine Überprüfung auf das Vorhandensein eines Elements hinzu:
<template> </template> <script> export default { methods: { logAndClean() { const { item } = this; if (!item) { return; } console.log(item.value); this.item = null; }, }, }; </script>
Um den Fehler zu reproduzieren, können Sie mit einem Beispiel zur Sandbox gehen, die maximale Drosselung der CPU einstellen und schnell auf die Schaltfläche klicken. Zum Beispiel habe ich es getan.
Sandbox-Link, um die Antwort sicherzustellenVielen Dank, dass Sie den Artikel bis zum Ende gelesen haben! Ich denke, dass Sie jetzt bei den Interviews sicherlich schlauer erscheinen können und die Gehälter Ihrer Kandidaten erheblich sinken werden!