8 Piores perguntas da entrevista no Vue.js.

Olá Habr!

Você gosta de entrevistas de emprego? E muitas vezes gastá-los? Se a resposta para a segunda pergunta for "Sim", provavelmente entre os candidatos você conheceu pessoas excelentes e inteligentes que responderam a todas as suas perguntas e se aproximaram do final da folha de pagamento.

Mas você, é claro, não quer pagar muito aos profissionais. E é extremamente necessário parecer mais esperto que eles, deixe-os apenas durante a entrevista.

Se você tiver problemas com isso, então seja bem-vindo ao gato. Lá, você encontrará as perguntas mais difíceis e perversas do Vue, que colocarão qualquer candidato no lugar e farão você duvidar de suas habilidades profissionais.

imagem

1. Gatilho do observador dentro dos ganchos do ciclo de vida


Essa pergunta pode parecer fácil, mas garanto que nem um, nem o desenvolvedor mais avançado, a responderá. Você pode perguntar a ele no início da entrevista, para que o candidato imediatamente sinta sua superioridade.

Pergunta:

Há um componente TestComponent que possui uma variável de quantidade. Dentro dos principais ganchos do ciclo de vida, definimos o valor em ordem numérica de 1 a 6. O Watcher, que exibe seu valor no console, fica nessa variável.

Criamos uma instância TestComponent e a excluímos após alguns segundos. Deve-se dizer que veremos na saída do console.

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> 

Vou dar uma dica: "2345" é a resposta errada.

A resposta
No console, veremos apenas o número 4.

Explicação
A instância em si ainda não foi criada no gancho beforeCreate, o watcher não funcionará aqui.

O Watcher aciona alterações nos ganchos criados, beforeMount e montados. Como todos esses ganchos são chamados durante um único tique, o Vue chama o observador uma vez no final, com um valor de 4.

O Vue cancelará a assinatura do monitoramento da alteração da variável antes de chamar os ganchos beforeDestroy e destruídos, para que 5 e 6 não cheguem ao console.

Caixa de areia com um exemplo para garantir que a resposta

2. Comportamento implícito dos adereços


Esta pergunta é baseada no comportamento raro de adereços no Vue. Todos os programadores, é claro, simplesmente expõem as validações necessárias para prop'ov e nunca encontram esse comportamento. Mas o candidato não precisa dizer isso. Seria melhor fazer essa pergunta, lançar um olhar de condenação depois de uma resposta incorreta e passar para a próxima.

Pergunta:

Como o prop com um tipo booleano difere do resto?

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

A resposta
O suporte com um tipo booleano difere de todos os outros, pois o Vue possui um tipo especial de conversão.

Se uma string vazia ou o nome do próprio prop no kebab-case for passado como parâmetro, o Vue converterá isso em true.

Um exemplo:

Temos um arquivo com prop booleano:

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

Todos os casos de uso válidos para o componente TestComponent são mostrados abaixo.

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


Caixa de areia com um exemplo para garantir que a resposta

3. Usando uma matriz em $ refs


Se o seu candidato sabe como a estrutura funciona de dentro para fora no nível Evan Yu, você ainda tem alguns trunfos na manga: você pode fazer uma pergunta sobre o comportamento não documentado e não óbvio da estrutura.

Pergunta:

O Vuex contém uma matriz de objetos de arquivos, cada um dos objetos na matriz possui propriedades exclusivas de nome e identificação. Essa matriz é atualizada a cada poucos segundos, os elementos são excluídos e adicionados a ela.

Temos um componente que exibe o nome de cada objeto da matriz com um botão, clicando no qual o elemento dom associado ao arquivo atual deve ser exibido no 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> 

Deve-se dizer onde está o erro em potencial e como corrigi-lo.

A resposta
O problema é que a matriz dentro de $ refs pode não ser da mesma ordem que a matriz original ( link para emitir ). Ou seja, essa situação pode ocorrer: clicamos no botão do terceiro elemento da lista e o elemento dom do segundo é exibido no console.

Isso acontece apenas quando os dados na matriz mudam com frequência.

Os métodos da solução estão escritos no GitHub:

1. Crie uma referência única para cada 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. 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. Recriação de componentes estranhos


Pergunta:

Temos um componente especial que grava no console toda vez que o gancho montado é chamado:

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


Este componente é usado no componente TestComponent. Ele tem um botão, pressionando o botão por 1 segundo a mensagem Top message será exibida.
 /* 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> 

Clique no botão e veja o que acontece no console:



A primeira montaria era esperada, mas onde estão as outras duas? Como consertar isso?

Caixa de areia com um exemplo para entender o erro e corrigi-lo

A resposta
O problema aqui surge das peculiaridades de procurar diferenças de DOMs virtuais no Vue.

No início, nosso DOM virtual se parece com isso:


Depois de clicar no botão, fica assim:



O Vue está tentando corresponder o antigo DOM Virtual com o novo para descobrir o que precisa ser removido e adicionado:


Os itens excluídos são riscados em vermelho, os itens criados são destacados em verde

O Vue não pôde encontrar o componente TestMount, portanto, o recriou.

Uma situação semelhante será repetida um segundo depois de pressionar o botão. Neste ponto, o componente TestMounted exibe informações sobre sua criação no console pela terceira vez.

Para corrigir o problema, basta colocar o atributo key na div com o componente TestMounted:

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

Agora o Vue poderá mapear sem ambiguidade os elementos necessários dos DOM virtuais.

5. Criando um componente de tabela


Desafio:

É necessário criar um componente que pega uma matriz com dados e os exibe em uma tabela. É necessário dar a oportunidade de especificar as colunas e o tipo de célula.

As informações sobre as colunas e o tipo de célula devem ser transmitidas através de um componente especial (o mesmo 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> 

No início, a tarefa não continha a necessidade de fazer o mesmo que o element-ui. Porém, algumas pessoas conseguem concluir a tarefa no texto original. Portanto, o requisito foi adicionado para transmitir informações sobre as colunas e o tipo de célula usando componentes.

Tenho certeza de que seus entrevistados ficarão estuporados o tempo todo. Você pode dar a eles 30 minutos para resolver esse problema.

Solução
A idéia principal é transferir todos os dados para o componente CustomTable no componente CustomColumn e, em seguida, ele renderizará tudo.

A seguir, é apresentado um exemplo de implementação. Não leva em consideração alguns pontos (como alterar o rótulo), mas o princípio básico deve 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. Criando um Portal


Se o seu candidato não concluiu a tarefa anterior, não há com que se preocupar: você pode dar a ele mais uma, não menos difícil!

Desafio:

Crie um componente Portal e PortalTarget, como a biblioteca portal-vue :

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

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

Solução
Para criar um portal, você precisa implementar três objetos:

  • Armazém de Dados do Portal
  • Componente do portal que adiciona dados à loja
  • O componente PortalTarget que recupera dados da loja e os exibe

 /* 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 solução não suporta a alteração do atributo para, não suporta animações por meio da transição e não suporta valores padrão, como portal-vue. Mas a ideia geral deve ser clara.

7. Prevenção de reatividade


Pergunta:

Você recebeu um objeto grande da API e o exibiu ao usuário. Algo assim:

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

Há um problema neste código. Não alteramos o nome, preço, qualidade e outras propriedades do objeto do item. Mas Vue não sabe disso e acrescenta reatividade a cada campo.

Como isso pode ser evitado?

A resposta
Para evitar alterar as propriedades para reativas, você deve congelar o objeto antes de adicioná-lo ao Vue usando o método Object.freeze.

O Vue verificará se o objeto está congelado usando o método Object.isFrozen. E, nesse caso, o Vue não adicionará getters e setters reativos às propriedades do objeto, pois eles não podem ser alterados em nenhum caso. Com objetos muito grandes, essa otimização ajuda a economizar várias dezenas de milissegundos.

O componente otimizado terá a seguinte aparência:

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

O Object.freeze congela apenas as propriedades do próprio objeto. Portanto, se um objeto contiver objetos aninhados, eles também precisarão ser congelados.

Atualização a partir de 19 de janeiro de 2019 : A conselho de Dmitry Zlygin, examinei a biblioteca vue-nonreactive e encontrei outra maneira. É perfeito para situações em que você tem muitos objetos aninhados.

O Vue não adicionará reatividade a um objeto se perceber que ele já é reativo. Podemos enganar o Vue criando um Observer vazio para o 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. Erros de dispositivos lentos


Pergunta:

Há um componente com um método que exibe uma das propriedades do objeto de item no console e remove o objeto de item:

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

O que poderia dar errado aqui?

A resposta
O problema é que, após o primeiro clique no botão Vue, leva algum tempo para atualizar o DOM do usuário e remover o botão. Portanto, às vezes, o usuário pode clicar duas vezes. O método logAndClean funciona normalmente na primeira vez e falha uma segunda vez, porque não pode obter a propriedade value.

Eu sempre vejo esse problema no rastreador de erros, especialmente em celulares baratos por 4-5k rublos.

Para evitá-lo, basta adicionar uma verificação da existência do item no início da função:

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

Para reproduzir o bug, você pode ir para a caixa de areia com um exemplo, definir a aceleração máxima da CPU e clicar rapidamente no botão. Por exemplo, eu fiz isso.



Link Sandbox para garantir que a resposta

Obrigado por ler o artigo até o fim! Acho que agora você pode parecer mais esperto nas entrevistas e os salários dos seus candidatos caem significativamente!

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


All Articles