blog

Vueに関するいくつかの質問

もしVueが非同期レンダリングに対応していなかったらと想像してみると、データに対する複数の更新を一度にレンダリングしなければならず、パフォーマンスが低下します。パフォーマンスを考慮し、Vue は非同期...

Aug 13, 2020 · 12 min. read
シェア
  • なぜVueで非同期レンダリング?
  1. Vueのレスポンシブデータが変更されると、dep.notify()メソッドが起動され、レンダリングウォッチャーに更新操作が通知されます。ウォッチャーのupdateメソッドは、現在のレンダリングウォッチャーをキューに入れます。同じものであれば、一度だけ追加できます;

  2. もしVueが非同期レンダリングを採用していなかったらと想像すると、データを何度も更新するとレンダリングが必要になり、パフォーマンスが低下します。ちなみに、非同期レンダリングのため、データを更新した直後にdom上の最新の値を取得することはできません。

  • nextTickの仕組みについて教えてください。
  1. 関係する知識ポイントは、マクロタスク、マイクロタスクです。ブラウザのイベントループは、同期タスクで発生した直接実行され、非同期タスクは、マクロタスク、マイクロタスクに分割され、実行の順序は、最初の同期タスクを実行し、すべてのマイクロタスクの実装では、マクロタスクキューにマクロタスクを追加することです、新しいマクロタスクの実装の完了など、類推ダウン。
// これは同期タスクである
console.log('1') 
// これはマクロ・タスクである
setTimeout(function () { 
 console.log('2') 
});
new Promise(function (resolve) {
 // 以下は同期タスクである。
 console.log('3'); 
 resolve(); 
 // thenこれはマイクロタスクである。
}).then(function () { 
 console.log('4') 
 setTimeout(function () {
 console.log('5')
 });
});
  1. promise.then(flushCallbacks) new MutationObserver(flushCallbacks)setImmediate(flushCallbacks)nextTickは、ビューの更新操作は、非同期実行のためのマイクロタスクまたはマクロタスクに詰め込まれている;Vueの最初のテストでは、プロミスをサポートするかどうか、コールバックの実行をサポートし、それ以外の場合は、MutationObserverをサポートするかどうかを確認し、それ以外の場合は、setImmediateをサポートするかどうかを確認し、それ以外の場合は、setImmediateをサポートするかどうかを確認します。setTimeout(flushCallbacks, 0)setImmediateをサポートしていない場合は、 に進みます;
  • Vueのレスポンシブ原則について
  1. Vueのレスポンシブとは何でしょうか?レスポンシブとは何かというと、データによってビューが更新されることです。この利点は、ビューレイヤーではなくデータレイヤーのみを気にすればよいということです。

  2. Object.definePropertyVueの中核となるレスポンシブメソッドは、ES5を使用してデータ操作を「ハイジャック」することです。データを読み込むときは、getメソッドを実行し、依存関係の収集操作を実行します。各依存関係は、現在のレンダリングウォッチャーまたはコンピューティングウォッチャーを収集します。データが変更されると、setメソッドが実行されます。これは、ビューを更新するupdateメソッドをトリガするために、すべての依存ウォッチャーに通知します。もちろん、データのインデックスなど、Vue2.Xのレスポンシブではまだ不可能なものがあります。もちろん、Vue2.XでもVue.$setメソッドやVue.$deleteメソッドなどの間接的なレスポンシブメソッドは提供されています。Vue3.XではProxyを使用することでこの問題を解決しています;

  • Vueでレスポンシブ配列の変更がどのように検出されるかご存知ですか?
  1. Vueの初期化は、データを再帰的に走査して各属性値をレスポンシブにします。配列の場合は、各項目を走査してレスポンシブにします;

  2. データの追加、削除、および操作の他のネイティブメソッドについては、私はVueは、より非常に巧妙な処理だと思う、メソッドのプロトタイプチェーンの配列を書き換えるの使用は、プッシュ、削除、スプライスネイティブメソッドを傍受するように、これはどのように実現するのですか?擬似コードは次のとおりです:

// array.js
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
...
methodsToPatch.forEach(function (method) {
 // cache original method
 const original = arrayProto[method]
 def(arrayMethods, method, function mutator (...args) {
 const result = original.apply(this, args)
 const ob = this.__ob__
 let inserted
 switch (method) {
 case 'push':
 case 'unshift':
 inserted = args
 break
 case 'splice':
 inserted = args.slice(2)
 break
 }
 if (inserted) ob.observeArray(inserted)
 // notify change
 ob.dep.notify()
 return result
 })
})
// observe.js
...
if (hasProto) { // ブラウザの互換性を判断する__prop__ 
 protoAugment(value, arrayMethods)
 } else {
 copyAugment(value, arrayMethods, arrayKeys)
 }
function protoAugment (target, src: Object) {
 /* eslint-disable no-proto */
 target.__proto__ = src // 配列が応答するように、配列のプロトタイプ・チェインを修正する(pop, push, shift, unshift, ... )
 /* eslint-enable no-proto */
}
...

プロトタイプチェーンをインターセプトすることにより、各追加値が応答し、手動で更新するトリガされます。VueのVue.$set、Vue.$deleteの実装では、原則のスプライスの配列を介して達成するためにもです;

  • Vueのwatchについて
  1. まず、ウォッチとは、レスポンシブ・データ・リスナーであり、データの変更をリスニングすると、コールバック関数をトリガーして複雑なロジック処理を行います;

  2. 具体的な使い方は以下の通り:

...
let app = new Vue({
 el: '#root',
 data() {
 return {
 msg: 'hello world'
 }
 },
 created() {
 },
 methods: {
 changeMsg() {
 this.msg = 'hello world!'
 }
 },
 watch: {
 msg(value) {
 console.log('value change', value)
 }
 }
})
...

ここでは繰り返しませんが、公式サイトで確認できます;

  1. 実装原理

    まず、VueはinitStateメソッドの初期化時に、ユーザが書き込んだ時計があるかどうかを検出し、あればinitWatchメソッドに入ります;
function initState(vm){
 ...
 if (opts.watch && opts.watch !== nativeWatch) {
 initWatch(vm, opts.watch);
 }
 ...
}

initWatch メソッドでは、opts.watch を走査してウォッチャーを作成します。createWatcher(vm, key, handler)

function createWatcher(vm, key, handler){
 ...
 return vm.$watch(expOrFn, handler, options)
}

options.immediate=trueさて、最終的にvm.$watchを通じて、時計の謎を明らかにした;実際には、ウォッチャーです。 しかし、ここでウォッチャーは、ユーザーウォッチャーとしてマークされ、ユーザー:true;ここでロジックがある、これは、ロジックの即時実装の上記の高度な使用の実装です;

Vue.prototype.$watch = function (
 expOrFn,
 cb,
 options
 ) {
 var vm = this;
 ...
 options = options || {};
 options.user = true;
 var watcher = new Watcher(vm, expOrFn, cb, options);
 if (options.immediate) {
 try {
 cb.call(vm, watcher.value);
 } catch (error) {
 handleError(error, vm, ("callback for immediate watcher "" + (watcher.expression) + """));
 }
 }
 ...
 };

ウォッチャーといえば、依存関係をウォッチャーにバインドする依存関係コレクションを思い浮かべるのは簡単です。watcher.jsに戻り、Vueがどのようにwatchを処理するか見てみましょう。

ウォッチャーの初期化では、ウォッチ式を処理するロジックがあります:

if (typeof expOrFn === 'function') { // レンダー関数
 this.getter = expOrFn
 } else {
 this.getter = parsePath(expOrFn)
 if (!this.getter) {
 this.getter = noop
 process.env.NODE_ENV !== 'production' && warn(
 `Failed watching path: "${expOrFn}" ` +
 'Watcher only accepts simple dot-delimited paths. ' +
 'For full control, use a function instead.',
 vm
 )
 }
}

以前はexpOrFnは文字列のキー値なので、それはparsePath(expOrFn)メソッドを移動されます渡されたウォッチを知ることができる、ここでは関数を返さコリアのスキルの関数の使用;その後、初期化はthisを呼び出すために重要なことを行うには。""".get()メソッド;これはthis.getterメソッドのトリガを行うメソッドは、それがレンダリングウォッチャーであれば、ビューを行います。このメソッドは、this.getterメソッドがトリガーとなり、レンダリングウォッチャーであれば、ビューを更新し、ユーザーウォッチャーであれば、値の依存関係を収集します。依存関係の値が変更されると、各ウォッチャーの更新メソッドがトリガーとなり、次の処理は、上記のレスポンシブ処理と同じです。

さて、以上がウォッチの紹介です。

  • Vue の computed プロパティについて教えてください。
  1. computed 属性とは何ですか? computed 属性はレスポンシブデータに基づいてキャッシュされ、レスポンシブデータが変更された場合にのみ再計算されます。この利点は、依存レスポンシブデータが変更されない限り、依存レスポンシブデータの get メソッドが複数回トリガーされないことです。

  2. 具体的な使い方は以下の通り:

...
let app = new Vue({
 el: '#root',
 data() {
 return {
 msg: 'hello world'
 }
 },
 computed:{
 computedMsg(){
 return this.msg+'!'
 }
 },
 created() {
 },
 methods: {
 changeMsg() {
 this.msg = 'hello world!'
 }
 }
 })
...
  1. 実装原理 まず最初に、VueはinitStateメソッドで計算された属性を検出すると、その属性で初期化します;
function initState(vm){
 ...
 if (opts.computed) { initComputed(vm, opts.computed); }
 ...
}

1: _computedWatchersオブジェクトがVueインスタンスにハングされます。2: _computedWatchersオブジェクトに、computed属性をトラバースしてcomputedウォッチャーが追加されます。defineComputed;ここで少しロジックがあります。ユーザがcomputed属性を手書きすると、getと書けるので、Vueはそれを処理します。また、computed属性がすでにvmに存在するかどうかのテストがあり、エラーメッセージを投げます。これもよくあるエラーです。結局のところ、computedという小さな結論もウォッチャーなのです。

 function initComputed (vm, computed) {
 // $flow-disable-line
 var watchers = vm._computedWatchers = Object.create(null);
 // computed properties are just getters during SSR
 var isSSR = isServerRendering();
 for (var key in computed) {
 var userDef = computed[key];
 var getter = typeof userDef === 'function' ? userDef : userDef.get;
 if (getter == null) {
 warn(
 ("Getter is missing for computed property "" + key + ""."),
 vm
 );
 }
 if (!isSSR) {
 // create internal watcher for the computed property.
 watchers[key] = new Watcher(
 vm,
 getter || noop,
 noop,
 computedWatcherOptions
 );
 }
 // component-defined computed properties are already defined on the
 // component prototype. We only need to define computed properties defined
 // at instantiation here.
 if (!(key in vm)) {
 defineComputed(vm, key, userDef);
 } else {
 if (key in vm.$data) {
 warn(("The computed property "" + key + "" is already defined in data."), vm);
 } else if (vm.$options.props && key in vm.$options.props) {
 warn(("The computed property "" + key + "" is already defined as a prop."), vm);
 }
 }
 }
 }

computedWatcherOptionsこれはウォッチャーでもあるので、watcher.jsにアクセスして、computedで何が行われているかを確認するのは良いアイデアです。computedウォッチャーを作成するときは、computed属性のキーであるパラメータを渡すことに注意してください。以下はその仕組みです:

 this.value = this.lazy // computedWatcherOptions  
 ? undefined
 : this.get()

computedウォッチャー初期化では、this.get() 呼び出しはありません;

// サーバー側のレンダリングロジックを削除する
function defineComputed (
 target,
 key,
 userDef
 ) {
 if (typeof userDef === 'function') {
 createComputedGetter(key)
 sharedPropertyDefinition.set = noop;
 } else {
 sharedPropertyDefinition.get = userDef.get
 ? createComputedGetter(key)
 : noop;
 sharedPropertyDefinition.set = userDef.set || noop;
 }
 if (sharedPropertyDefinition.set === noop) {
 sharedPropertyDefinition.set = function () {
 warn(
 ("Computed property "" + key + "" was assigned to but it has no setter."),
 this
 );
 };
 }
 Object.defineProperty(target, key, sharedPropertyDefinition);
 }

getcreateComputedGetterここで行われる主なことの1つは、計算される各プロパティに対応することです。その後、getメソッドとsetメソッドを手書きし、:

function createComputedGetter (key) {
 return function computedGetter () {
 var watcher = this._computedWatchers && this._computedWatchers[key];
 if (watcher) {
 if (watcher.dirty) {
 watcher.evaluate();
 }
 if (Dep.target) {
 watcher.depend();
 }
 return watcher.value
 }
 }
 }

まず、_computedWatchersはウォッチャーのcomputed属性で、キーに基づいて格納されているウォッチャーを取得し、次にdirty = trueなので計算が実行されます。この時、ウォッチャー内のgetメソッドがトリガーされます:

 evaluate () {
 this.value = this.get()
 this.dirty = false
 }

これは依存関係 this.msg を読み込み、msg から計算ウォッチャーを収集するトリガーとなります。計算ウォッチャーを収集するだけでは不十分で、レンダリングウォッチャーも収集する必要があります。Vueは別のウォッチャーを収集します。

 if (Dep.target) {
 watcher.depend();
 }

なぜこの方法でレンダリングされたウォッチャーを収集できるのですか?

ウォッチャーのgetメソッドは、ウォッチャーにpushTarget操作を実行し、どのようにそれを理解するには?まず、レンダリング関数の実装では、属性computedMsgを読み取る計算され、getメソッドと呼ばれる、pushTargetの実装では、この時点で[レンダリングウォッチャー]のtargetStack、およびその後、ウォッチャーの計算を収集するためにmsgを、getメソッドと呼ばれる、もう一度pushTargetのために[ウォッチャーのレンダリング、ウォッチャーの計算]この時、targetStackは[レンダリングウォッチャー]になり、ウォッチャーコレクションのレンダリングでmsgを完了するために非常に巧妙であることができます。

上記は計算されたcomputeプロパティの初期化分析ですが、依存関係の変化に応じてどのように再レンダリングされるのでしょうか?

計算属性の依存関係はすでに収集されているので、依存関係が変更されるとすぐに、計算ワッチャーとレンダリングウォッチャーが更新されます。そうです、ダーティ・フラグを変更します。計算ウォッチャーが更新されたときにdirty=trueを設定します:そして、レンダー・ウォッチャーが実行されたときに値の更新とビューの更新を行います;

update () {
 /* istanbul ignore else */
 if (this.lazy) { 
 this.dirty = true
 } else if (this.sync) { 
 this.run()
 } else {
 queueWatcher(this) 
 }
 }

我々はすべての計算されたデータをキャッシュする役割を持っていることを知って、キャッシュのための主な依存は何ですか?

ダーティマークに依存することです、ダーティな変更は、レスポンスデータの変更に依存し、限り、レスポンスデータが変更されないように再計算されませんが、計算された値を返します;

  • watch計算式との違い

fullName=firstName+lastnameまず、どちらもレスポンシブなデータに依存しており、computedはキャッシュ効果があり、依存しているデータが変更された場合のみ再計算を行うのに対し、watchはデータの変更をリッスンしている限りコールバック関数をトリガーするため、watchの方が非同期で複雑なロジック処理に向いています。公式サイトで比較デモが公開されているように、 、の時間を計算する場合、watchでデータの変更をリッスンする方が面倒なので、computedを使うことをおすすめします。を計算する場合、watch を使って listen すると面倒なので、computed を使うことをお勧めします。

  • v-forとv-ifの優先順位とパフォーマンスの改善方法?Vueの開発経験がある方なら、エディタにこのようなコードがあることをご存知でしょう:
<ul>
 <li v-for="(item,index) in 5" v-if="isExpand">
 {{item}}
 </li>
</ul>

同じ要素にv-ifとv-forを同時に使わないこと。コンソールに警告が出るように、公式サイトにも注意書きがあります。その具体的な理由は、公式サイトには記載されていませんが、コンパイルされたコードブロックの2種類を見て、比較対照して一目でわかるようになります。

  1. 優先順位の比較
if (el.staticRoot && !el.staticProcessed) {
 return genStatic(el, state)
 } else if (el.once && !el.onceProcessed) {
 return genOnce(el, state)
 } else if (el.for && !el.forProcessed) {
 return genFor(el, state)
 } else if (el.if && !el.ifProcessed) {
 return genIf(el, state)
 } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
 return genChildren(el, state) || 'void 0'
 } else if (el.tag === 'slot') {
 return genSlot(el, state)
 } else {
 ...
 }

このロジックから、for は if よりも優先されるようです:

 with(this) {
 return _c('div', {
 attrs: {
 "id": "app"
 }
 }, [_c('div', {
 staticClass: "lists"
 }, [_c('ul', _l((5), function (item, index) {
 return (isExpand) ? _c('li', [_v("
 " + _s(item) + "
")]) :
 _e()
 }), 0)])])
 }

c,_l,_lのロジックは無視してください。ここでのロジックは繰り返し処理を行い、v-if isExpandの3元式ロジックを処理します。書き込みと送信コードの別のブロックを見てみましょう;

 <ul v-if="isExpand">
 <li v-for="(item,index) in 5">
 {{item}}
 </li>
</ul>

コンパイルされたコードは以下の通り:

 with(this) {
 return _c('div', {
 attrs: {
 "id": "app"
 }
 }, [_c('div', {
 staticClass: "lists"
 }, [(isExpand) ? _c('ul', _l((5), function (item, index) {
 return _c('li', [_v("
 " + _s(item) + "
 ")])
 }), 0) : _e()])])
 }

このロジックからわかるように、3項式の計算が最初に実行されます。したがって、v-forの上にv-ifを書くことで、パフォーマンスを最適化することができます。

Read next

Mybatis学習

mavenを使用すると、mybatisをロードしません 1.Mavenを使用する - クラス1内のインタフェースの実装の理由は、メモリ内のオブジェクトの状態を保存したいときに

Aug 13, 2020 · 2 min read