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.

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 respostaNo console, veremos apenas o número 4.
ExplicaçãoA 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 resposta2. 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 respostaO 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> <TestComponent canShow="" /> <TestComponent canShow /> <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 resposta3. 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 respostaO 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-loA respostaO 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 verdeO 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çãoA 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.
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. 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çãoPara 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
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); }, }, };
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 respostaPara 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(); </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 respostaO 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 respostaObrigado 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!