エッジケースに対応する


このページは、コンポーネントの基本を読んでいることを前提としています。コンポーネントについてまだよく知らない場合は、最初に読むことをお勧めします。

ここに記録されているのは、エッジ ケース、つまり、Vue ルールに若干の調整を必要とする特殊なケースの処理に関連する関数です。ただし、これらの機能にはすべて欠点や危険なシナリオがあることに注意してください。その都度記載させていただきますので、各機能をご利用の際にはご注意ください。


#ディレクトリ

  • ##アクセス要素とコンポーネント

      #ルート インスタンスへのアクセス
    • ##親コンポーネント インスタンスへのアクセス
    • ##子コンポーネント インスタンスまたは子要素へのアクセス

    • 依存関係の挿入

    • プログラムによるイベント リスナー
  • 循環参照

  • ##再帰コンポーネント

    • コンポーネント間の循環参照

    • # #テンプレート定義の代替
  • #インライン テンプレート

  • 強制更新

    • v-once による低オーバーヘッドの静的コンポーネントの作成

  • 要素とコンポーネントへのアクセス


ほとんどの場合、別のコンポーネント インスタンスの内側または外側には触れないことが最善です。DOM 要素を手動で操作します。しかし、これらのことを行うことが適切な状況も確かにあります。



新しい Vue

のルート インスタンス

にアクセスします。インスタンスの子コンポーネントのうち、そのルート インスタンスには、$root プロパティを通じてアクセスできます。たとえば、次のルート インスタンスの場合:

// Vue 根实例
new Vue({
  data: {
    foo: 1
  },
  computed: {
    bar: function () { /* ... */ }
  },
  methods: {
    baz: function () { /* ... */ }
  }
})

すべての子コンポーネントは、このインスタンスにアクセスしたり、グローバル ストアとしてこのインスタンスを使用したりできます。

// 获取根组件的数据
this.$root.foo

// 写入根组件的数据
this.$root.foo = 2

// 访问根组件的计算属性
this.$root.bar

// 调用根组件的方法
this.$root.baz()

これは、デモやコンポーネント数が少ない非常に小さなアプリケーションに便利です。ただし、このモデルは中規模および大規模なアプリケーションには拡張されません。したがって、ほとんどの場合、Vuex を使用してアプリケーションの状態を管理することを強くお勧めします。


## 親コンポーネント インスタンス

および

$root にアクセスします。 同様に、次のようになります。 $parent プロパティを使用すると、子コンポーネントから親コンポーネントのインスタンスにアクセスできます。これにより、データを props の形式で子コンポーネントに渡すのではなく、後からいつでも親コンポーネントにアクセスできる機会が提供されます。

ほとんどの場合、親コンポーネントに到達すると、特に親コンポーネントのデータを変更する場合、アプリケーションのデバッグと理解がさらに困難になります。後でそのコンポーネントを振り返ってみると、その変更がどこから来たのかを理解するのは困難です。

さらに、必要に応じて、いくつかのコンポーネント ライブラリを具体的に共有する必要があります。たとえば、HTML をレンダリングせずに JavaScript API と対話する抽象コンポーネント内 (次のような仮想の Google マップ コンポーネント:

<google-map>
  <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
</google-map>

This

<google-map> コンポーネント) を定義できます。すべての子コンポーネントがアクセスする必要がある map 属性。この場合、<google-map-markers> this.$parent.getMap のようなものを介してその地図にアクセスし、一連のマーカーを追加することができます。このパターンは ここ で確認できます。

これにもかかわらず、このパターンで構築されたコンポーネントの内部には依然として問題が発生しやすいことに注意してください。たとえば、新しい

<google-map-region> コンポーネントを追加するとします。その中に <google-map-markers> が表示されると、その中のマーカーのみが表示されます。エリア:

<google-map>
  <google-map-region v-bind:shape="cityBoundaries">
    <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
  </google-map-region>
</google-map>

次に、

<google-map-markers> 内で次のようなハックが必要になるかもしれません:

var map = this.$parent.map || this.$parent.$parent.map

すぐに制御不能になります。これが、より深いコンポーネントにコンテキスト情報を提供する必要がある場合に

Dependency Injection をお勧めする理由です。


子コンポーネント インスタンスまたは子要素へのアクセス

プロパティやイベントが存在するにもかかわらず、場合によっては、 JavaScript で子コンポーネントに直接アクセスする必要がある場合もあります。これを実現するには、

ref 属性を使用して子コンポーネントに ID 参照を割り当てます。例:

<base-input ref="usernameInput"></base-input>

これで、この

ref を定義したコンポーネントで、次を使用できるようになります:

this.$refs.usernameInput

は、緊急時にこの <base-input> インスタンスにアクセスします。たとえば、プログラムによって親コンポーネントから入力ボックスにフォーカスします。先ほどの例では、<base-input> コンポーネントは、同様の ref を使用して、内部の指定された要素へのアクセスを提供することもできます。例:

<input ref="input">

親コンポーネントを通じてメソッドを定義することもできます:

methods: {
  // 用来从父级组件聚焦输入框
  focus: function () {
    this.$refs.input.focus()
  }
}

これにより、親コンポーネントは次のコードを介して <base-input> の入力ボックスに焦点を当てることができます:

this.$refs.usernameInput.focus()

refv-for とともに使用される場合、取得する参照は、対応するデータ ソースのこれらのサブコンポーネントを含む配列になります。

$refs は、コンポーネントがレンダリングされた後にのみ有効になり、応答しません。これは、子コンポーネントを直接操作するための「エスケープ ハッチ」としてのみ機能します。テンプレートまたは計算されたプロパティ内の $refs へのアクセスは避けてください。


Dependency Injection

その前に、親へのアクセスについて説明します。コンポーネント インスタンス のレベルの場合、次のような例が表示されました:

<google-map>
  <google-map-region v-bind:shape="cityBoundaries">
    <google-map-markers v-bind:places="iceCreamShops"></google-map-markers>
  </google-map-region>
</google-map>

このコンポーネントでは、<google-map> のすべての子孫が getMap にアクセスする必要があります。 どのマップを操作するかを知るためのメソッド。残念ながら、$parent 属性を使用すると、より深くネストされたコンポーネントにはうまく拡張できません。ここで、依存関係注入が登場します。provideinject という 2 つの新しいインスタンス オプションがあります。

provide オプションを使用すると、子孫コンポーネントに 提供するデータ/メソッドを指定できます。この例では、<google-map> 内の getMap メソッドです:

provide: function () {
  return {
    getMap: this.getMap
  }
}

その後、任意の子孫コンポーネントで

inject# を使用できます。 ## このインスタンスに追加する指定された属性を受け取るためのオプション:

inject: ['getMap']
完全な例は

ここ

で確認できます。 $parent と比較すると、この使用法では、<google-map> インスタンス全体を公開することなく、子孫コンポーネントの getMap にアクセスできます。これにより、サブコンポーネントが依存するものを変更/削除する可能性を心配することなく、コンポーネントの開発をより適切に継続できるようになります。同時に、これらのコンポーネント間のインターフェイスは、props のように常に明確に定義されます。 実際、依存関係注入は「広く有効なプロパティ」の一部と考えることができますが、次の点が異なります。

    祖先コンポーネントはどの子孫かを知る必要はありません。コンポーネントはそれを使用してプロパティを提供します
  • 子孫コンポーネントは、挿入されたプロパティの出所を知る必要はありません

ただし、依存関係の注入には依然として悪影響があります。アプリケーション内のコンポーネントが現在の編成方法に結合されるため、リファクタリングがより困難になります。また、提供されたプロパティは応答しません。これは仕様によるもので、これらを使用して一元化されたスケールのデータを作成し、$root を使用するだけでは十分ではありません。共有したいプロパティが汎用ではなくアプリケーションに固有の場合、または祖先コンポーネントで提供されたデータを更新したい場合は、 のような実際の状態管理のような Vuex に切り替える必要がある可能性があることを意味します。解決。

依存関係の挿入について詳しくは、

API リファレンス ドキュメントをご覧ください。


#プログラムによるイベント リスナー

もうおわかりでしょう
の使用法$emit

v-on でリッスンできますが、Vue インスタンスはイベント インターフェイスで他のメソッドも提供します。

    #Pass
  • $on(eventName,eventHandler)

    イベントをリッスンする

  • Pass
  • $ Once(eventName,eventHandler)

    一度にイベントをリッスンします

  • $off(eventName,eventHandler)

    を通じてイベントのリッスンを停止します

  • 通常はこれらを使用しませんが、コンポーネント インスタンスのイベントを手動でリッスンする必要がある場合に便利です。コード整理ツールでも使用できます。たとえば、サードパーティ ライブラリを統合する次のパターンをよく目にします:
// 一次性将这个日期选择器附加到一个输入框上
// 它会被挂载到 DOM 上。
mounted: function () {
  // Pikaday 是一个第三方日期选择器的库
  this.picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })
},
// 在组件被销毁之前,
// 也销毁这个日期选择器。
beforeDestroy: function () {
  this.picker.destroy()
}

ここには 2 つの潜在的な問題があります:

    コンポーネントのインスタンスが必要ですこの
  • picker

    を保存します。可能であれば、ライフサイクル フックのみがアクセスできるようにするのが最善です。これは深刻な問題ではありませんが、乱雑であると考えられる場合があります。

  • 私たちのビルド コードはクリーンアップ コードから独立しているため、ビルドするすべてのものをプログラムでクリーンアップすることが困難になります。
  • これら 2 つの問題は、プログラム リスナーを通じて解決する必要があります。
mounted: function () {
  var picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })
  this.$once('hook:beforeDestroy', function () {
    picker.destroy()
  })
}

この戦略を使用すると、異なる Pikaday を使用して、複数の入力ボックス要素を同時に持つこともできます。 、各新しいインスタンスは後でプログラムによって自動的にクリーンアップされます。

mounted: function () {
  this.attachDatepicker('startDateInput')
  this.attachDatepicker('endDateInput')
},
methods: {
  attachDatepicker: function (refName) {
    var picker = new Pikaday({
      field: this.$refs[refName],
      format: 'YYYY-MM-DD'
    })
    this.$once('hook:beforeDestroy', function () {
      picker.destroy()
    })
  }
}

完全なコードについては、

このフィドル

を確認してください。それでも、1 つのコンポーネントで多くのセットアップとクリーンアップ作業を実行する必要がある場合は、通常、より多くのモジュール式コンポーネントを作成することが最善であることに注意してください。この例では、再利用可能な <input-datepicker> コンポーネントを作成することをお勧めします。

プログラム リスナーの詳細については、インスタンス メソッド/イベント関連 API をご覧ください。

Vue のイベント システムはブラウザの EventTarget API とは異なることに注意してください。同様に動作しますが、$emit$on、および $off は ## の dispatchEventaddEventListener エイリアスではありません。 # と removeEventListener


#循環参照


再帰コンポーネント
コンポーネントは、独自のテンプレート内で自身を呼び出すことができます。ただし、これは

name

オプションを介してのみ実行できます:

name: 'unique-name-of-my-component'

Vue.component

を使用してコンポーネントをグローバルに登録すると、グローバル ID は自動的に次のように設定されます。このコンポーネントの name オプション。

Vue.component('unique-name-of-my-component', {
  // ...
})
注意しないと、再帰コンポーネントによって無限ループが発生する可能性があります:

name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'

上記と同様のコンポーネントは、「最大スタック サイズを超えました」エラーを引き起こすため、再帰呼び出しが正しく行われていることを確認してください。条件付き (例:

v-if

を使用すると、最終的に false になります)。


#コンポーネント間の循環参照

Like にアクセスするなど、ファイル ディレクトリ ツリーを構築する必要があるとします。ダーツとかエクスプローラーとか。次のようなテンプレートを持つ <tree-folder>

コンポーネントがあるとします:

<p>
  <span>{{ folder.name }}</span>
  <tree-folder-contents :children="folder.children"/>
</p>
そして <tree-folder-contents>

コンポーネント、テンプレート これは次のようになります:

<ul>
  <li v-for="child in children">
    <tree-folder v-if="child.children" :folder="child"/>
    <span v-else>{{ child.name }}</span>
  </li>
</ul>
よく見ると、これらのコンポーネントがレンダリング ツリー内で互いの子孫および祖先であることがわかります。これは矛盾しています。この矛盾は、コンポーネントが Vue.component

経由でグローバルに登録されると自動的に解決されます。これを行う場合は、これをスキップできます。

ただし、モジュール システムを使用して、webpack や Browserify などのコンポーネントに依存したり、コンポーネントをインポートしたりすると、次のエラーが発生します:

Failed to mount component: template or render function not defined.

ここで何が起こっているのかを説明するには、次のようにします。最初に 2 つのコンポーネントを置きます。コンポーネントは A と B と呼ばれます。モジュール システムは A が必要であることを発見しますが、最初に A は B に依存し、B は A に依存しますが、A は B に依存する、というようになります。これはループになり、一方のコンポーネントをもう一方を経由せずに完全に解析する方法がわかりません。この問題を解決するには、モジュール システムに「A にはとにかく B が必要だが、最初に B を解析する必要はない」というポイントを与える必要があります。

この例では、<tree-folder> コンポーネントをそのポイントに設定します。パラドックスを引き起こす子コンポーネントは <tree-folder-contents> コンポーネントであることがわかっているため、ライフサイクル フック beforeCreate がそれを登録するまで待ちます。

beforeCreate: function () {
  this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}

または、コンポーネントをローカルに登録するときに、webpack の非同期 import:

components: {
  TreeFolderContents: () => import('./tree-folder-contents.vue')
}

この問題は解決されました。


#テンプレート定義の代替


##インライン テンプレート
この特別な機能が子コンポーネントに表示される場合、コンポーネントはその内部のコンテンツを、作成するコンテンツとしてではなく、テンプレートとして使用します。配布されました。これにより、テンプレートの記述がより柔軟になります。

<my-component inline-template>
  <div>
    <p>These are compiled as the component's own template.</p>
    <p>Not parent's transclusion content.</p>
  </div>
</my-component>

インライン テンプレートは、Vue が属する DOM 要素内で定義する必要があります。

ただし、

inline-template

を使用すると、テンプレートの範囲がさらにわかりにくくなります。したがって、ベスト プラクティスとして、最初にコンポーネント内で
template

オプションを選択するか、.vue ファイル内の <template> 要素を選択してテンプレートを定義してください。

#X-Template

テンプレートを定義する別の方法は ## です。 #<script> 要素に text/x-template のタイプを指定し、ID を通じてテンプレートを参照します。例:

<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
  template: '#hello-world-template'
})
#xx-template は、Vue が属する DOM 要素の外側で定義する必要があります。

これらは、テンプレートが特に大きいデモや非常に小規模なアプリケーションで使用できますが、それ以外の場合は、コンポーネントの他の定義からテンプレートを分離するため、使用を避けてください。

コントロール更新


Vue の応答性の高いシステムのおかげで、常に情報を把握できますいつ更新するか(正しく実行した場合)。ただし、リアクティブ データが変更されていないように見える場合でも、強制的に更新する必要があるエッジ ケースがいくつかあります。不必要な更新を防ぎたい場合もあります。



強制更新


Vue で更新する必要がある場合強制アップデートを実行する場合、99.9% の確率で、どこかで何か間違った操作を行っています。 配列

または
オブジェクト

の変更検出に関する考慮事項に気づいていないか、Vue のリアクティブ システムによって追跡されないものに依存している可能性があります。状態。

ただし、上記の手順を実行しても、まれに手動で強制的にアップデートする必要がある場合は、$forceUpdate を使用してこれを行うことができます。


v-once

レンダリングによる低オーバーヘッドの静的コンポーネントの作成Vue では通常の HTML 要素は非常に高速ですが、場合によっては、コンポーネントに 多くの 静的コンテンツが含まれる場合があります。この場合、次のように、ルート要素に v-once 属性を追加して、コンテンツが 1 回だけ計算されてキャッシュされるようにすることができます:

Vue.component('terms-of-service', {
  template: `
    <div v-once>
      <h1>Terms of Service</h1>
      ... a lot of static content ...
    </div>
  `
})

One more time 、このパターンを多用しないようにしてください。まれに、大量の静的コンテンツをレンダリングする必要がある場合、これは便利ですが、レンダリングの速度低下をよく認識していない限り、完全に不要です - さらに、ポストで多くの問題を引き起こすことになります。たとえば、別の開発者が v-once に精通していないか、テンプレートにそれが含まれていない場合、テンプレートが正しく更新されない理由を解明するのに何時間も費やす可能性があります。