8 peores preguntas de la entrevista de Vue.js

Hola Habr!

¿Te gustan las entrevistas de trabajo? ¿Y a menudo los gastan? Si la respuesta a la segunda pregunta es "Sí", entonces entre los candidatos probablemente conoció a personas excelentes e inteligentes que respondieron todas sus preguntas y se acercaron al final de la nómina.

Pero usted, por supuesto, no quiere pagar demasiado a los profesionales. Y es vitalmente necesario parecer más listo que ellos, dejarlos solo durante la entrevista.

Si tienes problemas con esto, entonces bienvenido a cat. Allí encontrará las preguntas más difíciles y perversas en Vue, que pondrán a cualquier candidato en su lugar y lo harán dudar de sus habilidades profesionales.

imagen

1. Vigilante disparador dentro de ganchos de ciclo de vida


Esta pregunta puede parecer fácil, pero garantizo que nadie, ni siquiera el desarrollador más avanzado, la responderá. Puede preguntarle al comienzo de la entrevista, para que el candidato inmediatamente sienta su superioridad.

Pregunta:

Hay un componente TestComponent que tiene una cantidad variable. Dentro de los ganchos principales del ciclo de vida, establecemos el valor en orden numérico de 1 a 6. Watcher, que muestra su valor en la consola, se encuentra en esta variable.

Creamos una instancia de TestComponent y la eliminamos después de unos segundos. Hay que decir que veremos en la salida de la consola.

Código:

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

Daré una pista: "2345" es la respuesta incorrecta.

La respuesta
En la consola, solo veremos el número 4.

Explicación
La instancia en sí aún no se ha creado en el enlace beforeCreate, el observador no funcionará aquí.

El Observador desencadena cambios en los ganchos creados, antes de montaje y montados. Como todos estos ganchos se llaman durante un tic, Vue llamará al observador una vez al final, con un valor de 4.

Vue cancelará la suscripción del monitoreo del cambio de la variable antes de llamar a beforeDestroy y los ganchos destruidos, por lo que 5 y 6 no llegarán a la consola.

Sandbox con un ejemplo para asegurarte la respuesta

2. Comportamiento implícito de accesorios


Esta pregunta se basa en el raro comportamiento de los accesorios en Vue. Todos los programadores, por supuesto, simplemente exponen las validaciones necesarias para prop'ov y nunca encuentran tal comportamiento. Pero el candidato no necesita decir esto. Sería mejor hacer esta pregunta, echarle una mirada condenatoria después de una respuesta incorrecta y pasar a la siguiente.

Pregunta:

¿En qué se diferencia el accesorio con un tipo booleano del resto?

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

La respuesta
El accesorio con un tipo booleano difiere de todos los demás en que Vue tiene un tipo de molde especial para él.

Si una cadena vacía o el nombre del accesorio en sí mismo en kebab-case se pasa como parámetro, Vue lo convertirá en verdadero.

Un ejemplo:

Tenemos un archivo con utilería booleana:

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

Todos los casos de uso válidos para el componente TestComponent se muestran a continuación.

 /* 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 con un ejemplo para asegurarte la respuesta

3. Usando una matriz en $ refs


Si su candidato sabe cómo funciona el marco desde adentro hacia afuera en el nivel de Evan Yu, todavía tiene algunas cartas de triunfo bajo la manga: puede hacer una pregunta sobre el comportamiento indocumentado y no obvio del marco.

Pregunta:

Vuex contiene una matriz de objetos de archivos, cada uno de los objetos en la matriz tiene un nombre único y propiedades de identificación. Esta matriz se actualiza cada pocos segundos, los elementos se eliminan y se le agregan.

Tenemos un componente que muestra el nombre de cada objeto de matriz con un botón, haciendo clic en el cual el elemento dom asociado con el archivo actual debe mostrarse en la consola:

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

Hay que decir dónde está el error potencial y cómo solucionarlo.

La respuesta
El problema es que la matriz dentro de $ refs puede no ir en el mismo orden que la matriz original ( enlace para emitir ). Es decir, tal situación puede ocurrir: hacemos clic en el botón del tercer elemento de la lista, y el elemento dom del segundo se muestra en la consola.

Esto solo ocurre cuando los datos en la matriz cambian con frecuencia.

Los métodos de solución están escritos en cuestión en GitHub:

1. Crea una referencia única para cada artículo

 <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. Atributo adicional

 <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. Extraña componente de recreación


Pregunta:

Tenemos un componente especial que escribe en la consola cada vez que se llama al gancho montado:

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


Este componente se usa en el componente TestComponent. Tiene un botón, al presionar que durante 1 segundo aparecerá el mensaje Mensaje superior.
 /* 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> 

Haga clic en el botón y vea qué sucede en la consola:



Se esperaba el primer montaje, pero ¿dónde están los otros dos? ¿Cómo arreglarlo?

Sandbox con un ejemplo para comprender el error y solucionarlo

La respuesta
El problema aquí surge de las peculiaridades de buscar diferencias de DOM virtuales en Vue.

Al principio, nuestro DOM virtual se ve así:


Después de hacer clic en el botón, se ve así:



Vue está tratando de hacer coincidir el antiguo DOM virtual con el nuevo para descubrir qué debe eliminarse y agregarse:


Los elementos eliminados se tachan en rojo, los elementos creados se resaltan en verde

Vue no pudo encontrar el componente TestMount, por lo tanto, lo recreó.

Una situación similar se repetirá un segundo después de presionar el botón. En este punto, el componente TestMounted muestra información sobre su creación en la consola por tercera vez.

Para solucionar el problema, simplemente coloque el atributo clave en el div con el componente TestMounted:

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

Ahora Vue podrá asignar inequívocamente los elementos necesarios de DOM virtuales.

5. Crear un componente de tabla


Desafío:

Es necesario crear un componente que tome una matriz con datos y los muestre en una tabla. Es necesario dar la oportunidad de especificar las columnas y el tipo de celda.

La información sobre las columnas y el tipo de celda debe transmitirse a través de un componente especial (igual que 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> 

Al principio, la tarea no contenía la necesidad de hacer lo mismo que element-ui. Pero resultó que algunas personas pueden completar la tarea en la redacción original. Por lo tanto, se agregó el requisito de transmitir información sobre las columnas y el tipo de celda que usa componentes.

Estoy seguro de que sus entrevistados estarán en estado de estupor todo el tiempo. Puede darles 30 minutos para resolver tal problema.

Solución
La idea principal es transferir todos los datos al componente CustomTable en el componente CustomColumn, y luego representará todo por sí mismo.

El siguiente es un ejemplo de implementación. No tiene en cuenta algunos puntos (como cambiar la etiqueta), pero el principio básico debe ser claro.

 /* 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. Crear un portal


Si su candidato no ha completado la tarea anterior, no hay nada de qué preocuparse: ¡puede darle una más, no menos difícil!

Desafío:

Cree un componente Portal y PortalTarget, como la biblioteca portal-vue :

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

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

Solución
Para crear un portal, debe implementar tres objetos:

  • Almacén de datos del portal
  • Componente de portal que agrega datos a la tienda
  • El componente PortalTarget que recupera datos de la tienda y los muestra

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

Esta solución no admite cambiar el atributo a, no admite animaciones a través de la transición y no admite valores predeterminados, como portal-vue. Pero la idea general debe ser clara.

7. Prevención de la reactividad.


Pregunta:

Recibió un objeto grande de la API y se lo mostró al usuario. Algo como esto:

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

Hay un problema en este código. No cambiamos el nombre, precio, calidad y otras propiedades del objeto objeto. Pero Vue no sabe sobre esto y agrega reactividad a cada campo.

¿Cómo se puede evitar esto?

La respuesta
Para evitar cambiar las propiedades a reactivo, debe congelar el objeto antes de agregarlo dentro de Vue utilizando el método Object.freeze.

Vue verificará si el objeto está congelado utilizando el método Object.isFrozen. Y si es así, entonces Vue no agregará getters y setters reactivos a las propiedades del objeto, ya que no se pueden cambiar en ningún caso. Con objetos muy grandes, esta optimización ayuda a ahorrar hasta varias decenas de milisegundos.

El componente optimizado se verá así:

 /* 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 congela solo las propiedades del objeto mismo. Entonces, si un objeto contiene objetos anidados, también deben ser congelados.

Actualización a partir del 19 de enero de 2019 : siguiendo el consejo de Dmitry Zlygin, miré la biblioteca vue-nonreactive y encontré otra forma. Es perfecto para situaciones en las que tiene muchos objetos anidados.

Vue no agregará reactividad a un objeto si ve que ya es reactivo. Podemos engañar a Vue creando un Observador vacío para el objeto:

 /* 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. Errores de dispositivos lentos


Pregunta:

Hay un componente con un método que muestra una de las propiedades del objeto de elemento en la consola y luego elimina el objeto de elemento:

 /* 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é podría salir mal aquí?

La respuesta
El problema es que después del primer clic en el botón Vue, lleva algún tiempo actualizar el DOM para el usuario y eliminar el botón. Por lo tanto, el usuario a veces puede hacer doble clic. El método logAndClean funciona normalmente la primera vez y se bloquea por segunda vez, porque no puede obtener la propiedad value.

Constantemente veo ese problema en el rastreador de errores, especialmente a menudo en teléfonos móviles baratos por 4-5k rublos.

Para evitarlo, simplemente agregue una marca de verificación de la existencia del elemento al comienzo de la función:

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

Para reproducir el error, puede ir al sandbox con un ejemplo, establecer la aceleración máxima de la CPU y hacer clic rápidamente en el botón. Por ejemplo, lo hice.



Enlace de caja de arena para asegurarse de que la respuesta

¡Gracias por leer el artículo hasta el final! ¡Creo que ahora ciertamente puede parecer más inteligente en las entrevistas y los salarios de sus candidatos caerán significativamente!

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


All Articles