Behandeln Sie Randfälle


Auf dieser Seite wird davon ausgegangen, dass Sie Komponentengrundlagen gelesen haben. Wenn Sie noch nicht viel über Komponenten wissen, empfehle ich Ihnen, es zuerst zu lesen.

Was hier aufgezeichnet wird, sind Funktionen im Zusammenhang mit der Handhabung von Randfällen, also einigen Sonderfällen, die einige kleine Anpassungen der Vue-Regeln erfordern. Bitte beachten Sie jedoch, dass diese Funktionen alle Nachteile oder gefährliche Szenarien haben. Wir werden dies jeweils vermerken, achten Sie daher bitte bei der Nutzung der jeweiligen Funktion darauf.


Verzeichnis


Zugriffselemente & Komponenten


In den meisten Fällen ist es am besten, nicht das Innere einer anderen Komponenteninstanz zu berühren oder DOM-Elemente manuell zu manipulieren. Aber es gibt sicherlich Situationen, in denen es angebracht ist, diese Dinge zu tun.


Greifen Sie auf die Stamminstanz

in einer untergeordneten Komponente jeder new Vue-Instanz zu , auf deren Stamminstanz über das Attribut $root zugegriffen werden kann. In dieser Stamminstanz beispielsweise:

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

Alle untergeordneten Komponenten können auf diese Instanz zugreifen oder sie als globalen Speicher verwenden.

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

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

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

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

Dies ist praktisch für Demos oder sehr kleine Anwendungen mit einer kleinen Anzahl von Komponenten. Dieses Modell erstreckt sich jedoch nicht auf mittlere und große Anwendungen. Daher empfehlen wir in den meisten Fällen dringend, Vuex zur Verwaltung des Anwendungsstatus zu verwenden.


Zugriff auf übergeordnete Komponenteninstanz

ähnelt $root, $parent Eigenschaften können verwendet werden, um von einer untergeordneten Komponente aus auf eine Instanz einer übergeordneten Komponente zuzugreifen. Es bietet die Möglichkeit, jederzeit später auf die übergeordnete Komponente zuzugreifen, anstatt Daten in Form von Requisiten an die untergeordnete Komponente zu übergeben.

In den meisten Fällen wird das Erreichen der übergeordneten Komponente das Debuggen und Verstehen Ihrer Anwendung erschweren, insbesondere wenn Sie die Daten der übergeordneten Komponente ändern. Wenn wir später auf diese Komponente zurückblicken, ist es schwierig herauszufinden, wo diese Änderung ihren Ursprung hat.

Darüber hinaus müssen Sie, wenn es angebracht ist, einige Komponentenbibliotheken gezielt freigeben. Zum Beispiel innerhalb einer abstrakten Komponente, die mit der JavaScript-API interagiert, ohne HTML zu rendern, wie diese hypothetischen Google Maps-Komponenten:

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

Die <google-map>-Komponente kann ein map-Attribut definieren, das alle untergeordneten Komponenten benötigen um darauf zuzugreifen. In diesem Fall möchte <google-map-markers> möglicherweise über etwas wie this.$parent.getMap auf die Karte zugreifen, um ihr eine Reihe von Markierungen hinzuzufügen. Sie können sich dieses Muster hier ansehen.

Bitte beachten Sie, dass die Interna der nach diesem Muster erstellten Komponente jedoch immer noch anfällig für Probleme sind. Stellen Sie sich zum Beispiel vor, wir fügen eine neue <google-map-region>-Komponente hinzu. Wenn <google-map-markers> darin erscheint, wird nur das Markup in diesem Bereich gerendert:

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

Dann finden Sie im Inneren von <google-map-markers> möglicherweise einige Hacks wie Das für sich selbst:

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

Es wird ziemlich schnell außer Kontrolle geraten. Aus diesem Grund empfehlen wir Dependency Injection, wenn Sie einer tiefer liegenden Komponente kontextbezogene Informationen bereitstellen müssen.


Zugriff auf untergeordnete Komponenteninstanzen oder untergeordnete Elemente

Trotz der Existenz von Requisiten und Ereignissen manchmal Möglicherweise müssen Sie dennoch direkt in JavaScript auf eine untergeordnete Komponente zugreifen. Um dies zu erreichen, können Sie der Kindkomponente über das Attribut ref eine ID-Referenz zuweisen. Zum Beispiel:

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

Jetzt können Sie in der Komponente, in der Sie dieses ref definiert haben, Folgendes verwenden:

this.$refs.usernameInput

um in Notfällen auf diese <base-input> Instanz zuzugreifen. Fokussieren Sie beispielsweise programmgesteuert das Eingabefeld einer übergeordneten Komponente. Im Beispiel gerade kann die <base-input>-Komponente auch ein ähnliches ref verwenden, um Zugriff auf das angegebene Element darin bereitzustellen, zum Beispiel:

<input ref="input">

kann sogar Methoden über seine übergeordnete Komponente definieren:

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

Dadurch kann die übergeordnete Komponente über den folgenden Code fokussiert werden<base-input> Eingabefeld in:

this.$refs.usernameInput.focus()

Wenn ref und v-for zusammen verwendet werden, ist die Referenz, die Sie erhalten, ein Array, das diese Unterkomponenten der entsprechenden Datenquelle enthält.

$refs werden erst wirksam, nachdem die Komponente das Rendern abgeschlossen hat, und sie reagieren nicht. Dies dient nur als „Flucht“ für die direkte Manipulation untergeordneter Komponenten – Sie sollten den Zugriff auf $refs in Vorlagen oder berechneten Eigenschaften vermeiden.


Abhängigkeitsinjektion

Vorher, bevor wir es beschreibenBeim Zugriff auf die übergeordnete Komponenteninstanz wird ein Beispiel ähnlich diesem angezeigt:

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

In dieser Komponente alle Nachkommen von <google-map> müssen auf eine getMap-Methode zugreifen, um zu wissen, mit welcher Karte sie interagieren sollen. Leider lässt sich die Verwendung des $parent-Attributs nicht gut auf tiefer verschachtelte Komponenten skalieren. Hier kommt die Abhängigkeitsinjektion ins Spiel, die zwei neue Instanzoptionen verwendet: provide und inject. Mit der Option

provide können wir die Daten/Methoden angeben, die wir den Nachkommenkomponenten bereitstellen möchten. In diesem Beispiel ist es die <google-map>-Methode in getMap:

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

Dann können wir in jeder Nachkommenkomponente die Option inject verwenden, um den angegebenen Wert zu erhalten, den wir diesen Instanzeigenschaften hinzufügen möchten von:

inject: ['getMap']

Das vollständige Beispiel können Sie hier sehen. Im Vergleich zu $parent ermöglicht uns diese Verwendung den Zugriff auf getMap in jeder untergeordneten Komponente, ohne die gesamte <google-map>-Instanz verfügbar zu machen. Dadurch können wir die Komponente besser weiterentwickeln, ohne befürchten zu müssen, dass wir etwas ändern/entfernen, von dem Unterkomponenten abhängen. Gleichzeitig ist die Schnittstelle zwischen diesen Komponenten immer klar definiert, genau wie props.

Tatsächlich kann man sich die Abhängigkeitsinjektion als Teil der „allgemein gültigen Requisite“ vorstellen, außer:

  • Die Vorgängerkomponente muss nicht wissen, welche Nachkommenkomponenten es sind Verwenden Sie es, um Eigenschaften bereitzustellen

  • Nachkommenkomponenten müssen nicht wissen, woher die injizierten Eigenschaften stammen

Abhängigkeitsinjektion hat jedoch immer noch negative Auswirkungen. Es koppelt die Komponenten in Ihrer Anwendung an die Art und Weise, wie sie aktuell organisiert sind, was die Umgestaltung erschwert. Außerdem reagieren die bereitgestellten Eigenschaften nicht. Dies ist beabsichtigt, da ihre Verwendung zur Erstellung einer zentralisierten Datenskala nicht ausreicht, um $root dafür zu verwenden. Wenn die Eigenschaft, die Sie freigeben möchten, anwendungsspezifisch und nicht generisch ist oder Sie die bereitgestellten Daten in einer Vorgängerkomponente aktualisieren möchten, bedeutet dies, dass Sie möglicherweise zu einem Vuex wie Vuex wechseln müssen eine echte State-Management-Lösung.

Weitere Informationen zur Abhängigkeitsinjektion finden Sie im API-Referenzdokument.


Programmatische Event-Listener


Jetzt wissen Sie es< Verwendung von 🎜>, es kann von $emit angehört werden, aber Vue Instanzen stellen in ihrer Ereignisschnittstelle auch andere Methoden bereit. Wir können: v-on

  • Ein Ereignis anhören über

    $on(eventName, eventHandler)

  • Ein Ereignis gleichzeitig anhören über

    $once(eventName, eventHandler)

  • Bestanden

    Hören Sie auf, auf ein Ereignis zu lauschen $off(eventName, eventHandler)

Sie werden diese normalerweise nicht verwenden, aber sie sind praktisch, wenn Sie manuell auf Ereignisse auf einer Komponenteninstanz warten müssen. Sie können auch in Code-Organisationstools verwendet werden. Beispielsweise sehen Sie häufig dieses Muster bei der Integration einer Bibliothek eines Drittanbieters:

// 一次性将这个日期选择器附加到一个输入框上
// 它会被挂载到 DOM 上。
mounted: function () {
  // Pikaday 是一个第三方日期选择器的库
  this.picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })
},
// 在组件被销毁之前,
// 也销毁这个日期选择器。
beforeDestroy: function () {
  this.picker.destroy()
}

Hier gibt es zwei potenzielle Probleme:

  • Es ist eine Instanz der Komponente erforderlich Speichern Sie dies in

    , damit möglichst nur Lifecycle-Hooks darauf zugreifen können. Dies ist kein ernstes Problem, kann aber als Unordnung angesehen werden. picker

  • Unser Build-Code ist unabhängig von unserem Bereinigungscode, was es schwieriger macht, alles, was wir erstellen, programmgesteuert zu bereinigen.

Sie sollten beide Probleme durch einen programmatischen Listener lösen:

mounted: function () {
  var picker = new Pikaday({
    field: this.$refs.input,
    format: 'YYYY-MM-DD'
  })
  this.$once('hook:beforeDestroy', function () {
    picker.destroy()
  })
}

Mit dieser Strategie kann ich sogar mehrere Eingabefeldelemente gleichzeitig haben. Mit einem anderen Pikaday, Jede neue Instanz bereinigt sich programmgesteuert im Post:

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()
    })
  }
}

Schauen Sie sich das an

das fiddle kann den vollständigen Code lernen. Beachten Sie, dass es in der Regel am besten ist, mehr modulare Komponenten zu erstellen, wenn Sie viele Einrichtungs- und Aufräumarbeiten in einer einzelnen Komponente durchführen müssen. In diesem Beispiel empfehlen wir die Erstellung einer wiederverwendbaren -Komponente. <input-datepicker>

Um mehr über programmatische Listener zu erfahren, schauen Sie sich bitte die Instanzmethoden/-ereignisse bezogenen APIs an.

Beachten Sie, dass sich das Ereignissystem von Vue von der EventTarget-API des Browsers unterscheidet. Obwohl sie ähnlich funktionieren, sind $emit, $on und $off keine Aliase für dispatchEvent, addEventListener und removeEventListener.


Zirkelverweis


Rekursive Komponenten

Komponenten können sich in ihren eigenen Vorlagen selbst aufrufen. Dies ist jedoch nur über die Option name möglich:

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

Wenn Sie eine Komponente global mit Vue.component registrieren, wird die globale ID automatisch auf die Option name der Komponente gesetzt.

Vue.component('unique-name-of-my-component', {
  // ...
})

Wenn Sie nicht aufpassen, können rekursive Komponenten Endlosschleifen verursachen:

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

Komponenten wie die oben genannten verursachen den Fehler „Maximale Stapelgröße überschritten“. Stellen Sie daher bitte sicher, dass der rekursive Aufruf an Bedingungen geknüpft ist (z. B. mit einem false, das mit v-if endet).


Zirkelverweise zwischen Komponenten

Angenommen, Sie müssen einen Dateiverzeichnisbaum erstellen, z. B. access Like Dart oder Explorer. Möglicherweise haben Sie eine <tree-folder>-Komponente mit einer Vorlage, die so aussieht:

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

und eine <tree-folder-contents>-Komponente mit einer Vorlage, die so aussieht:

<ul>
  <li v-for="child in children">
    <tree-folder v-if="child.children" :folder="child"/>
    <span v-else>{{ child.name }}</span>
  </li>
</ul>

Wenn Sie genau hinsehen, erkennen Sie Es stellt sich heraus, dass diese Komponenten im Rendering-Baum Abkömmlinge und Vorfahren voneinander sind – ein Paradoxon! Dieses Paradox wird automatisch gelöst, wenn Komponenten global über Vue.component registriert werden. Wenn Sie dies tun, können Sie dies überspringen.

Wenn Sie jedoch ein Modulsystem verwenden, um Komponenten abzuhängen/zu importieren, z. B. über Webpack oder Browserify, wird eine Fehlermeldung angezeigt:

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

Um zu erklären, was hier vor sich geht, lassen Sie uns Folgendes tun Setzen Sie zuerst die beiden Komponenten ein, die als A und B bezeichnet werden. Das Modulsystem stellt fest, dass es A benötigt, aber zuerst hängt A von B ab, aber B hängt von A ab, aber A hängt von B ab und so weiter. Dies wird zu einer Schleife und ich weiß nicht, wie ich eine der Komponenten vollständig analysieren kann, ohne die andere zu durchlaufen. Um dieses Problem zu lösen, müssen wir dem Modulsystem einen Punkt geben, an dem „A ohnehin B braucht, aber wir müssen B nicht zuerst analysieren.“

In unserem Beispiel stellen Sie die Komponente <tree-folder> auf diesen Punkt ein. Wir wissen, dass die untergeordnete Komponente, die das Paradoxon verursacht, die <tree-folder-contents>-Komponente ist, daher warten wir, bis der Lebenszyklus-Hook beforeCreate sie registriert:

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

Alternativ können Sie bei der lokalen Registrierung der Komponente Folgendes verwenden: Webpack Asynchronous import:

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

Dieses Problem ist gelöst!


Ersatz für Vorlagendefinition


Inline-Vorlage

Wann inline-template Wenn diese spezielle Funktion in einer untergeordneten Komponente angezeigt wird, verwendet die Komponente den darin enthaltenen Inhalt als Vorlage, anstatt ihn als verteilten Inhalt zu verwenden. Dies macht das Schreiben von Vorlagen flexibler.

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

Inline-Vorlagen müssen innerhalb des DOM-Elements definiert werden, zu dem Vue gehört.

Allerdings macht inline-template den Umfang der Vorlage schwieriger zu verstehen. Als Best Practice wählen Sie bitte die Option template innerhalb der Komponente oder ein .vue-Element in der Datei <template> aus, um die Vorlage zu definieren.


X-Template

Eine andere Möglichkeit, eine Vorlage zu definieren, ist in einem <script> Element, geben Sie ihm den Typ text/x-template und übergeben Sie dann eine ID Verweisen Sie auf die Vorlage. Beispiel:

<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
  template: '#hello-world-template'
})

x-template muss außerhalb des DOM-Elements definiert werden, zu dem Vue gehört.

Diese können für Demos oder sehr kleine Anwendungen verwendet werden, bei denen die Vorlage besonders groß ist. Ansonsten vermeiden Sie bitte die Verwendung, da dadurch die Vorlage von anderen Definitionen der Komponente getrennt wird.


Updates steuern


Dank des reaktionsfähigen Systems von Vue weiß es immer Bescheid wann aktualisiert werden muss (wenn Sie es richtig machen). Es gibt jedoch Randfälle, in denen Sie eine Aktualisierung erzwingen möchten, obwohl sich die reaktiven Daten scheinbar nicht geändert haben. Es gibt auch Situationen, in denen Sie unnötige Updates verhindern möchten.


Update erzwingen

Wenn Sie feststellen, dass Sie ein Update in Vue benötigen Bei einem erzwungenen Update haben Sie in 99,9 % der Fälle irgendwo etwas falsch gemacht.

Möglicherweise haben Sie die Überlegungen zur Änderungserkennung für Arrays oder Objekte nicht bemerkt, oder Sie verlassen sich möglicherweise auf eines, das nicht vom reaktiven System von Vue verfolgt wird Status.

Wenn Sie jedoch die oben genannten Schritte ausgeführt haben und dennoch in seltenen Fällen feststellen, dass Sie ein Update manuell erzwingen müssen, können Sie dies über $forceUpdate tun.


Erstellen Sie statische Komponenten mit geringem Overhead über v-once

Rendern Sie gewöhnliche HTML-Elemente in Vue ist sehr schnell, aber manchmal haben Sie möglicherweise eine Komponente, die viele statische Inhalte enthält. In diesem Fall können Sie das Attribut v-once zum Stammelement hinzufügen, um sicherzustellen, dass diese Inhalte nur einmal berechnet und zwischengespeichert werden, wie folgt:

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

Auch hier sollten Sie versuchen, es nicht zu übertreiben . In den seltenen Fällen, in denen Sie viele statische Inhalte rendern müssen, ist dies praktisch, aber wenn Sie nicht sehr auf die Verlangsamung des Renderns achten, ist dies völlig unnötig – und es wird beim Posten viel Ärger verursachen. Stellen Sie sich beispielsweise vor, dass ein anderer Entwickler mit v-once nicht vertraut ist oder es in der Vorlage übersieht. Er verbringt möglicherweise viele Stunden damit, herauszufinden, warum die Vorlage nicht korrekt aktualisiert wird.