最悪のVue.jsインタビューの質問8

こんにちは、Habr!



就職の面接が好きですか? そして、しばしばそれらを過ごす? 2番目の質問の答えが「はい」の場合、候補者の間で、あなたはすべての質問に答え、給与計算の終わりに近づいた優秀で頭の良い人たちに会ったでしょう。



しかし、もちろん、あなたは専門家にあまりお金をかけたくありません。 そして、彼らよりも賢く見えることが極めて必要であり、インタビューの間だけ彼らに任せてください。



これに問題がある場合は、catへようこそ。 そこでは、Vueで最も巧妙でひねくれた質問を見つけることができます。これは、候補者を配置し、専門的なスキルを疑います。



画像



1.ライフサイクルフック内のウォッチャートリガー



この質問は簡単に思えるかもしれませんが、私は、たとえ最も先進的な開発者であっても、誰も答えないことを保証しています。 面接の最初に彼に尋ねて、候補者がすぐにあなたの優位性を感じるようにすることができます。



質問:



量変数を持つTestComponentコンポーネントがあります。 ライフサイクルのメインフック内で、1〜6の数値順に値を設定します。コンソールに値を表示するWatcherは、この変数の上に立ちます。



TestComponentインスタンスを作成し、数秒後に削除します。 コンソール出力に表示されることを言わなければなりません。



コード:



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





ヒントを挙げます。「2345」は間違った答えです。



答え
コンソールでは、4番のみが表示されます。



説明
インスタンス自体はbeforeCreateフックでまだ作成されていません。ウォッチャーはここでは動作しません。



ウォッチャーは、作成されたフック、beforeMountフック、およびマウントされたフックへの変更をトリガーします。 これらのフックはすべて1ティック中に呼び出されるため、Vueは最後に1回、値4でウォッチャーを呼び出します。



Vueは、beforeDestroyおよびdestroyフックを呼び出す前に変数の変更の監視を解除します。そのため、5および6はコンソールに到達しません。



答えを確認するための例のあるサンドボックス



2.暗黙の小道具の動作



この質問は、Vueのまれな小道具の動作に基づいています。 もちろん、すべてのプログラマーは、prop'ovに必要な検証を公開するだけで、このような動作に遭遇することはありません。 しかし、候補者はこれを言う必要はありません。 この質問をして、間違った答えの後に非難の目線を向け、次の質問に進む方が良いでしょう。



質問:



ブール型の小道具の動作は、他とどのように異なりますか?



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





答え
ブール型のプロップは、Vueが特別な型キャストを行うという点で他のすべてと異なります。



空の文字列またはケバブケース内のプロップ自体の名前がパラメーターとして渡される場合、Vueはこれをtrueに変換します。



例:



ブール型のpropを持つファイルがあります。



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





TestComponentコンポーネントのすべての有効な使用法を以下に示します。



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







答えを確認するための例のあるサンドボックス



3. $ refsで配列を使用する



候補者がフレームワークがEvan Yuレベルで内部からどのように機能するかを知っている場合、まだいくつかの切り札があります。フレームワークの文書化されていない明白な動作について質問することができます。



質問:



Vuexにはファイルオブジェクトの配列が含まれ、配列内の各オブジェクトには一意の名前とIDプロパティがあります。 この配列は数秒ごとに更新され、要素が削除されて追加されます。



現在のファイルに関連付けられているdom要素をコンソールに表示するためにクリックすることで、ボタンで各配列オブジェクトの名前を表示するコンポーネントがあります。



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





潜在的なエラーはどこにあり、それを修正する方法を言わなければなりません。



答え
問題は、$ refs内の配列が元の配列と同じ順序にならない場合があることです( 問題へのリンク )。 つまり、このような状況が発生する可能性があります。リストの3番目の要素のボタンをクリックすると、2番目のdom要素がコンソールに表示されます。



これは、配列内のデータが頻繁に変更される場合にのみ発生します。



ソリューションメソッドはGitHubで問題に書かれています。



1.各アイテムに一意の参照を作成します



 <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.追加属性



 <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.奇妙なコンポーネントの再作成



質問:



マウントされたフックが呼び出されるたびにコンソールに書き込む特別なコンポーネントがあります。



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







このコンポーネントは、TestComponentコンポーネントで使用されます。 ボタンがあり、これを押すと、Top messageというメッセージが1秒間表示されます。

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





ボタンをクリックして、コンソールで何が起こるかを確認します。







最初のマウントが予想されていましたが、他の2つはどこですか? 修正方法



エラーを理解して修正するためのサンプルを含むサンドボックス



答え
ここでの問題は、Vueの仮想DOM間の違いを見つける特性のために発生します。



最初は、仮想DOMは次のようになります。









ボタンをクリックすると、次のようになります。











Vueは、削除および追加する必要があるものを理解するために、古い仮想DOMを新しい仮想DOMにマップしようとしています。









削除されたアイテムは赤色で消され、作成されたアイテムは緑色で強調表示されます



VueはTestMountコンポーネントを見つけることができなかったため、再作成しました。



同様の状況は、ボタンを押してから1秒後に繰り返されます。 この時点で、TestMountedコンポーネントは、その作成に関する情報を3回目にコンソールに出力します。



この問題を修正するには、TestMountedコンポーネントを使用してdivにキー属性を配置するだけです:



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





これで、Vueは仮想DOMの必要な要素を明確にマッピングできるようになります。



5.テーブルコンポーネントの作成



チャレンジ:



データの配列を受け入れ、テーブルに表示するコンポーネントを作成する必要があります。 列とセルのタイプを指定する機会を与える必要があります。



列とセルのタイプに関する情報は、特別なコンポーネント( 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>
      
      





最初は、タスクにはelement-uiと同じことをする必要はありませんでした。 しかし、元の文言でタスクを完了することができる人もいることが判明しました。 そのため、コンポーネントを使用して列とセルのタイプに関する情報を転送する要件が追加されました。



あなたのインタビューの対象者は常にst迷するでしょう。 そのような問題を解決するために、30分間与えることができます。



解決策
主なアイデアは、すべてのデータをCustomColumnコンポーネント内のCustomTableコンポーネントに転送し、すべてをレンダリングすることです。



以下は実装例です。 一部のポイント(ラベルの変更など)は考慮されていませんが、基本原則は明確になっているはずです。



 /* 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.ポータルの作成



あなたの候補者が前のタスクで失敗した場合、心配することは何もありません。あなたは彼にもう1つ与えることができます。



チャレンジ:



portal-vueライブラリのようなPortalおよびPortalTargetコンポーネントを作成します。



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





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





解決策
ポータルを作成するには、3つのオブジェクトを実装する必要があります。



  • ポータルデータウェアハウス
  • ストレージにデータを追加するポータルコンポーネント
  • ストアからデータを取得して表示するPortalTargetコンポーネント


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





このソリューションは、to属性の変更をサポートせず、移行によるアニメーションをサポートせず、portal-vueなどのデフォルト値もサポートしません。 しかし、一般的な考え方は明確でなければなりません。



7.反応性の防止



質問:



APIからラージオブジェクトを受け取り、ユーザーに表示しました。 このようなもの:



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





このコードに問題があります。 アイテムオブジェクトの名前、価格、品質、その他のプロパティは変更しません。 しかし、Vueはこれについて知らず、各フィールドに反応性を追加します。



どうすればこれを回避できますか?



答え
プロパティをリアクティブに変更しないようにするには、Object.freezeメソッドを使用してVueに追加する前にオブジェクトをフリーズする必要があります。



Vueは、Object.isFrozenメソッドを使用してオブジェクトが凍結されているかどうかを確認します。 その場合、Vueはオブジェクトのプロパティにリアクティブゲッターとセッターを追加しません。これらはいずれの場合も変更できないためです。 オブジェクトが非常に大きい場合、この最適化は最大数十ミリ秒の節約に役立ちます。



最適化されたコンポーネントは次のようになります。



 /* 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は、オブジェクト自体のプロパティのみをフリーズします。 そのため、オブジェクトにネストされたオブジェクトが含まれている場合は、それらも凍結する必要があります。



2019年 1月19日の更新Dmitry Zlyginのアドバイスで、 vue-nonreactiveライブラリを見て、別の方法を見つけました。 ネストされたオブジェクトがたくさんある状況に最適です。



Vueは、既に反応していることがわかると、オブジェクトに反応性を追加しません。 オブジェクトに対して空のオブザーバーを作成することにより、Vueをだますことができます。



 /* 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.低速デバイスのエラー



質問:



コンソールにアイテムオブジェクトのプロパティの1つを表示し、アイテムオブジェクトを削除するメソッドを持つコンポーネントがあります。



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





ここで何が間違っているのでしょうか?



答え
問題は、Vueボタンを最初にクリックした後、ユーザーのDOMを更新してボタンを削除するのに時間がかかることです。 そのため、ユーザーはダブルクリックできる場合があります。 logAndCleanメソッドは最初は正常に機能し、valueプロパティを取得できないため、2回目にクラッシュします。



エラートラッカー、特に4〜5kルーブルの安価な携帯電話では、このような問題が常に発生します。



それを回避するには、関数の先頭にアイテムの存在のチェックを追加するだけです:



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





バグを再現するには、例を使用してサンドボックスに移動し、CPUの最大スロットルを設定して、ボタンをクリックします。 たとえば、私はそれをやった。











答えを確認するためのサンドボックスリンク



最後まで記事を読んでくれてありがとう! インタビューでは確かに賢く見えるようになり、候補者の給与は大幅に下がると思います!



All Articles