Rendering-Funktionen und JSX
Inhaltsverzeichnis
Grundlagen
Vue In den meisten Fällen empfiehlt es sich, für die HTML-Erstellung Vorlagen zu verwenden. Es gibt jedoch einige Szenarien, in denen Sie wirklich die vollständigen Programmierfunktionen von JavaScript benötigen. Zu diesem Zeitpunkt können Sie die Rendering-Funktion verwenden, die näher am Compiler als an der Vorlage liegt.
Sehen wir uns ein einfaches Beispiel an, bei dem die render
-Funktion sehr nützlich ist. Angenommen, wir möchten einige verankerte Titel generieren:
<h1> <a name="hello-world" href="#hello-world"> Hello world! </a> </h1>
Für den obigen HTML-Code entscheiden Sie sich, die Komponentenschnittstelle wie folgt zu definieren:
<anchored-heading :level="1">Hello world!</anchored-heading>
Wenn Sie mit dem Schreiben eines Titels beginnen, der nur dynamisch generiert werden kann über die level
prop-Überschrift Wenn Sie eine Komponente verwenden, denken Sie vielleicht schnell darüber nach, sie so zu implementieren:
<script type="text/x-template" id="anchored-heading-template"> <h1 v-if="level === 1"> <slot></slot> </h1> <h2 v-else-if="level === 2"> <slot></slot> </h2> <h3 v-else-if="level === 3"> <slot></slot> </h3> <h4 v-else-if="level === 4"> <slot></slot> </h4> <h5 v-else-if="level === 5"> <slot></slot> </h5> <h6 v-else-if="level === 6"> <slot></slot> </h6> </script>rrree
Die Verwendung einer Vorlage ist hier nicht die beste Wahl: Der Code ist nicht nur langwierig, sondern auch <slot></slot>
wird immer wieder in den Titel jedes Levels geschrieben . Dies wiederholt sich noch einmal beim Einfügen von Ankerelementen.
Während Vorlagen in den meisten Komponenten gut funktionieren, passen sie hier offensichtlich nicht. Versuchen wir also, das obige Beispiel mit der Funktion render
umzuschreiben:
Vue.component('anchored-heading', { template: '#anchored-heading-template', props: { level: { type: Number, required: true } } })
Sieht viel einfacher aus! Auf diese Weise ist der Code viel einfacher, Sie müssen jedoch mit den Instanzeigenschaften von Vue sehr vertraut sein. In diesem Beispiel müssen Sie wissen, dass, wenn Sie untergeordnete Knoten ohne eine v-slot
-Anweisung an eine Komponente übergeben, z. B. anchored-heading
innerhalb eines Hello world!
, diese untergeordneten Knoten in $slots.default
in der Komponenteninstanz gespeichert werden. Wenn Sie es noch nicht verstehen, empfehlen Sie, die Instance Properties API zu lesen, bevor Sie sich mit der Rendering-Funktion befassen.
Knoten, Bäume und virtuelles DOM
Bevor wir in die Rendering-Funktion eintauchen, lassen Sie uns Es ist wichtig, ein paar zu verstehen. Wie der Browser funktioniert. Nehmen Sie das folgende HTML-Stück als Beispiel:
Vue.component('anchored-heading', { render: function (createElement) { return createElement( 'h' + this.level, // 标签名称 this.$slots.default // 子节点数组 ) }, props: { level: { type: Number, required: true } } })
Wenn der Browser diesen Code liest, erstellt er einen "DOM-Knoten"-Baum, um den Überblick über alles zu behalten, genau wie Sie zeichnen gerne einen Stammbaum, um die Entwicklung von Familienmitgliedern zu verfolgen.
Der dem obigen HTML entsprechende DOM-Knotenbaum ist wie folgt:
Jedes Element ist ein Knoten. Jeder Text ist auch ein Knoten. Sogar Kommentare sind Knoten. Ein Knoten ist ein Teil einer Seite. Genau wie bei einem Stammbaum kann jeder Knoten Kinder haben (d. h. jeder Teil kann andere Teile enthalten).
Es kann schwierig sein, alle diese Knoten effizient zu aktualisieren, aber zum Glück müssen Sie dies nicht manuell tun. Sie müssen Vue nur mitteilen, welchen HTML-Code Sie auf der Seite haben möchten. Dies kann in einer Vorlage erfolgen:
<div> <h1>My title</h1> Some text content <!-- TODO: Add tagline --> </div>
oder in einer Renderfunktion:
<h1>{{ blogTitle }}</h1>
In beiden Fällen wird Vue die Seite erstellen automatisch aktualisiert, auch wenn sich blogTitle
ändert.
Virtuelles DOM
Vue verfolgt sich selbst, indem es ein Virtuelles DOM erstellt. Wie um das echte DOM zu ändern. Bitte schauen Sie sich diese Codezeile genau an:
render: function (createElement) { return createElement('h1', this.blogTitle) }
createElement
Was genau wird zurückgegeben? Eigentlich kein tatsächliches DOM-Element. Der genauere Name lautet möglicherweise createNodeDescription
, da die darin enthaltenen Informationen Vue mitteilen, welche Art von Knoten auf der Seite gerendert werden muss, einschließlich der Beschreibungsinformationen seiner untergeordneten Knoten. Wir beschreiben einen solchen Knoten als „virtuellen Knoten“ und kürzen ihn oft als „VNode“ ab. „Virtuelles DOM“ nennen wir den gesamten VNode-Baum, der aus dem Vue-Komponentenbaum erstellt wurde.
createElement
Parameter
Als nächstes müssen Sie sich mit der Verwendung dieser Funktionen in der Vorlage im <🎜 vertraut machen > Funktion. Hier sind die von createElement
akzeptierten Argumente: createElement
return createElement('h1', this.blogTitle)
Deep Data Object
Eines ist zu beachten: als und v-bind:class
Da sie in der Vorlagensyntax speziell behandelt werden, verfügen sie auch über entsprechende Felder der obersten Ebene im VNode-Datenobjekt. Mit diesem Objekt können Sie auch normale HTML-Attribute sowie DOM-Attribute wie v-bind:style
binden (dies überschreibt die innerHTML
-Direktive). v-html
// @returns {VNode} createElement( // {String | Object | Function} // 一个 HTML 标签名、组件选项对象,或者 // resolve 了上述任何一种的一个 async 函数。必填项。 'div', // {Object} // 一个与模板中属性对应的数据对象。可选。 { // (详情见下一节) }, // {String | Array} // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成, // 也可以使用字符串来生成“文本虚拟节点”。可选。 [ '先写一些文字', createElement('h1', '一则头条'), createElement(MyComponent, { props: { someProp: 'foobar' } }) ] )
Vollständiges Beispiel
Mit diesem Wissen können wir nun meine vervollständigen Die Komponente, die wir ursprünglich implementieren wollten:{ // 与 `v-bind:class` 的 API 相同, // 接受一个字符串、对象或字符串和对象组成的数组 'class': { foo: true, bar: false }, // 与 `v-bind:style` 的 API 相同, // 接受一个字符串、对象,或对象组成的数组 style: { color: 'red', fontSize: '14px' }, // 普通的 HTML 特性 attrs: { id: 'foo' }, // 组件 prop props: { myProp: 'bar' }, // DOM 属性 domProps: { innerHTML: 'baz' }, // 事件监听器在 `on` 属性内, // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。 // 需要在处理函数中手动检查 keyCode。 on: { click: this.clickHandler }, // 仅用于组件,用于监听原生事件,而不是组件内部使用 // `vm.$emit` 触发的事件。 nativeOn: { click: this.nativeClickHandler }, // 自定义指令。注意,你无法对 `binding` 中的 `oldValue` // 赋值,因为 Vue 已经自动为你进行了同步。 directives: [ { name: 'my-custom-directive', value: '2', expression: '1 + 1', arg: 'foo', modifiers: { bar: true } } ], // 作用域插槽的格式为 // { name: props => VNode | Array<VNode> } scopedSlots: { default: props => createElement('span', props.text) }, // 如果组件是其它组件的子组件,需为插槽指定名称 slot: 'name-of-slot', // 其它特殊顶层属性 key: 'myKey', ref: 'myRef', // 如果你在渲染函数中给多个元素都应用了相同的 ref 名, // 那么 `$refs.myRef` 会变成一个数组。 refInFor: true }
Constraints
VNode Muss eindeutig sein
Alle VNodes im Komponentenbaum müssen eindeutig sein. Das bedeutet, dass die folgende Rendering-Funktion illegal ist:var getChildrenTextContent = function (children) { return children.map(function (node) { return node.children ? getChildrenTextContent(node.children) : node.text }).join('') } Vue.component('anchored-heading', { render: function (createElement) { // 创建 kebab-case 风格的 ID var headingId = getChildrenTextContent(this.$slots.default) .toLowerCase() .replace(/\W+/g, '-') .replace(/(^-|-$)/g, '') return createElement( 'h' + this.level, [ createElement('a', { attrs: { name: headingId, href: '#' + headingId } }, this.$slots.default) ] ) }, props: { level: { type: Number, required: true } } })Wenn Sie ein Element/eine Komponente wirklich viele Male wiederholen müssen, können Sie eine Factory-Funktion verwenden, um dies zu erreichen. Beispielsweise rendert die folgende Rendering-Funktion 20 identische Absätze auf völlig legale Weise:
render: function (createElement) { var myParagraphVNode = createElement('p', 'hi') return createElement('div', [ // 错误 - 重复的 VNode myParagraphVNode, myParagraphVNode ]) }
unter Verwendung von JavaScript Ersetzen Sie die Vorlagenfunktion
v-if
v-if
v-for
und v-if
v-for
Die Renderfunktionen von Vue bieten keine proprietären Alternativen zu irgendetwas, das einfach in nativem JavaScript erledigt werden kann. Beispielsweise werden
in Vorlagen verwendet: if/else
render: function (createElement) { return createElement('div', Array.apply(null, { length: 20 }).map(function () { return createElement('p', 'hi') }) ) }
map
Diese können in der Rendering-Funktion mithilfe von JavaScripts und verwendet werden Zum Umschreiben:
<ul v-if="items.length"> <li v-for="item in items">{{ item.name }}</li> </ul> <p v-else>No items found.</p>
v-model
v-model
v-model
v-model
Rendering-Funktion Keine mit Direkte Korrespondenz – Sie müssen die entsprechende Logik selbst implementieren:
props: ['items'], render: function (createElement) { if (this.items.length) { return createElement('ul', this.items.map(function (item) { return createElement('li', item.name) })) } else { return createElement('p', 'No items found.') } }Dies ist der Preis dafür, tief in die unterste Ebene vorzudringen, aber im Vergleich zu
erhalten Sie dadurch eine bessere Kontrolle über die Interaktionsdetails.
.passive
Ereignis- und Tastenmodifikatoren .capture
.once
on
修饰符 | 前缀 |
---|---|
.passive | & |
.capture | ! |
.once | ~ |
.capture.once 或.once.capture | ~! |
Zum Beispiel:
props: ['value'], render: function (createElement) { var self = this return createElement('input', { domProps: { value: self.value }, on: { input: function (event) { self.$emit('input', event.target.value) } } }) }
Für alle anderen Modifikatoren ist das private Präfix nicht notwendig, da Sie die Ereignismethode im Ereignishandler verwenden können:
修饰符 | 处理函数中的等价操作 |
---|---|
.stop | event.stopPropagation() |
.prevent | event.preventDefault() |
.self | if (event.target !== event.currentTarget) return |
按键:.enter , .13 | if (event.keyCode !== 13) return (对于别的按键修饰符来说,可将 13 改为另一个按键码) |
修饰键:.ctrl , .alt , .shift , .meta | if (!event.ctrlKey) return (将 ctrlKey 分别修改为 altKey 、shiftKey 或者 metaKey ) |
Hier ist ein Beispiel mit allen Modifikatoren:
on: { '!click': this.doThisInCapturingMode, '~keyup': this.doThisOnce, '~!mouseover': this.doThisOnceInCapturingMode }
slots
you Du kannst bestehen this.$slots
Greifen Sie auf den Inhalt statischer Slots zu, jeder Slot ist ein VNode-Array:
on: { keyup: function (event) { // 如果触发事件的元素不是事件绑定的元素 // 则返回 if (event.target !== event.currentTarget) return // 如果按下去的不是 enter 键或者 // 没有同时按下 shift 键 // 则返回 if (!event.shiftKey || event.keyCode !== 13) return // 阻止 事件冒泡 event.stopPropagation() // 阻止该元素默认的 keyup 事件 event.preventDefault() // ... } }
Sie können auch über this.$scopedSlots
auf Scope-Slots zugreifen, jeder Scope-Slot ist eine Funktion, die mehrere VNodes zurückgibt:
render: function (createElement) { // `<div><slot></slot></div>` return createElement('div', this.$slots.default) }
Wenn Sie die Rendering-Funktion verwenden möchten, um den Scope-Slot an die untergeordnete Komponente zu übergeben, können Sie VNode verwenden scopedSlots
Feld im Datenobjekt:
props: ['message'], render: function (createElement) { // `<div><slot :text="message"></slot></div>` return createElement('div', [ this.$scopedSlots.default({ text: this.message }) ]) }
JSX
Wenn Sie A schreiben vielrender
Funktion kann es schwierig sein, den folgenden Code zu schreiben:
render: function (createElement) { return createElement('div', [ createElement('child', { // 在数据对象中传递 `scopedSlots` // 格式为 { name: props => VNode | Array<VNode> } scopedSlots: { default: function (props) { return createElement('span', props.text) } } }) ]) }
Vor allem, wenn die entsprechende Vorlage so einfach ist:
createElement( 'anchored-heading', { props: { level: 1 } }, [ createElement('span', 'Hello'), ' world!' ] )
Deshalb gibt es ein Babel Plugin wird verwendet, um die JSX-Syntax in Vue zu verwenden, wodurch wir zu einer Syntax zurückkehren können, die näher an Vorlagen liegt.
<anchored-heading :level="1"> <span>Hello</span> world! </anchored-heading>
Die Verwendung von
h
als Alias fürcreateElement
ist eine gängige Konvention im Vue-Ökosystem und wird tatsächlich von JSX benötigt. Ab der 3.4.0-Version des Babel-Plug-Ins von Vue werden wirconst h = this.$createElement
automatisch in jede Methode und jeden Getter einfügen, die JSX enthalten, die in der ES2015-Syntax deklariert sind (nicht in Funktionen oder Pfeilfunktionen), damit Sie Sie können den Parameter(h)
entfernen. Bei früheren Versionen des Plugins gibt die Anwendung einen Fehler aus, wennh
im aktuellen Bereich nicht verfügbar ist.
Um mehr über die Zuordnung von JSX zu JavaScript zu erfahren, lesen Sie die Verwendungsdokumentation.
Funktionskomponente
Die zuvor erstellte Ankertitelkomponente ist relativ einfach verwaltet keinen Status, überwacht keinen an ihn übergebenen Status und verfügt über keine Lebenszyklusmethoden. Tatsächlich handelt es sich lediglich um eine Funktion, die einige Requisiten akzeptiert.
In einem solchen Szenario können wir die Komponente als functional
markieren, was bedeutet, dass sie zustandslos ist (keine responsive Daten) und keine Instanzen hat (kein this
-Kontext). ).
Eine Funktionskomponente sieht so aus:
import AnchoredHeading from './AnchoredHeading.vue' new Vue({ el: '#demo', render: function (h) { return ( <AnchoredHeading level={1}> <span>Hello</span> world! </AnchoredHeading> ) } })
Hinweis: In Versionen vor 2.3.0 war die Option
props
erforderlich, wenn eine Funktionskomponente Requisiten empfangen wollte. In Version 2.3.0 oder höher können Sie die Optionprops
weglassen und alle Eigenschaften der Komponente werden automatisch und implizit in Requisiten aufgelöst.
Bei der Verwendung funktionaler Komponenten lautet die Referenz HTMLElement, da diese zustands- und instanzlos sind.
Wenn Sie in Version 2.5.0 und höher die Einzeldateikomponente verwenden, kann die vorlagenbasierte Funktionskomponente wie folgt deklariert werden:
Vue.component('my-component', { functional: true, // Props 是可选的 props: { // ... }, // 为了弥补缺少的实例 // 提供第二个参数作为上下文 render: function (createElement, context) { // ... } })
Alles, was die Komponente benötigt, wird übergeben context
Parameterübergabe, bei der es sich um ein Objekt mit den folgenden Feldern handelt:
props
: ein Objekt, das alle Requisiten bereitstelltchildren
: VNode Array von untergeordneten Knotenslots
: Eine Funktion, die ein Objekt zurückgibt, das alle Slots enthältscopedSlots
: (2.6. 0 ) Ein Objekt, das den übergebenen Bereichsslot verfügbar macht. Stellt auch normale Slots als Funktionen bereit.data
: Das gesamte Datenobjekt , das alscreateElement
an die Komponente übergeben wurde Der zweite an die Komponente übergebene Parameterparent
: ein Verweis auf die übergeordnete Komponentelisteners
: (2.3.0 ) Ein Objekt, das alle von übergeordneten Komponenten für die aktuelle Komponente registrierten Ereignis-Listener enthält. Dies ist ein Alias für data.on.
verwendet wird die Eigenschaften, die injiziert werden sollen.injections
: (2.3.0 ) Dieses Objekt enthält, wenn die Optioninject
inject
functional: true
Nach dem Hinzufügen von context
müssen wir die Rendering-Funktion unserer Ankertitelkomponente aktualisieren, den Parameter this.$slots.default
hinzufügen und context.children
auf this.level
aktualisieren. Aktualisieren Sie dann context.props.level
auf
- Wählen Sie programmgesteuert eine von mehreren Komponenten aus, die in Ihrem Namen gerendert werden sollen
children
Im 🎜> ,props
,data
bearbeiten sie, bevor sie an untergeordnete Komponenten übergeben werden.
Das Folgende ist ein Beispiel für eine smart-list
-Komponente, die Requisiten übergeben kann um eine spezifischere Komponente zu rendern:
<template functional> </template>
Attribute und Ereignisse an untergeordnete Elemente oder untergeordnete Komponenten übergeben
In gewöhnlichen Komponenten werden Attribute, die nicht als Requisiten definiert sind, automatisch zum Stammelement der Komponente hinzugefügt und ersetzen vorhandene Attribute mit demselben Namen oder intelligenter Zusammenführung .
Für funktionale Komponenten müssen Sie dieses Verhalten jedoch explizit definieren:
var EmptyList = { /* ... */ } var TableList = { /* ... */ } var OrderedList = { /* ... */ } var UnorderedList = { /* ... */ } Vue.component('smart-list', { functional: true, props: { items: { type: Array, required: true }, isOrdered: Boolean }, render: function (createElement, context) { function appropriateListComponent () { var items = context.props.items if (items.length === 0) return EmptyList if (typeof items[0] === 'object') return TableList if (context.props.isOrdered) return OrderedList return UnorderedList } return createElement( appropriateListComponent(), context.data, context.children ) } })
Indem wir createElement
als zweites Argument an context.data
übergeben, geben wir my-functional-button
alle oben genannten Eigenschaften und Event-Listener werden weitergegeben. Tatsächlich ist es so transparent, dass für diese Ereignisse nicht einmal der Modifikator .native
erforderlich ist.
Wenn Sie vorlagenbasierte Funktionskomponenten verwenden, müssen Sie Attribute und Listener auch manuell hinzufügen. Da wir Zugriff auf den unabhängigen Kontext haben, können wir jedes HTML-Attribut mit data.attrs
und jeden Ereignis-Listener mit listeners
(ein Alias für data.on
) übergeben.
Vue.component('my-functional-button', { functional: true, render: function (createElement, context) { // 完全透传任何特性、事件监听器、子节点等。 return createElement('button', context.data, context.children) } })
slots()
vs. children
Sie fragen sich vielleicht, warum beides nötig istslots()
und children
. Ist slots().default
nicht ähnlich zu children
? In manchen Szenarien trifft das zu – aber was ist, wenn es sich um eine funktionale Komponente mit untergeordneten Knoten wie den folgenden handelt?
<template functional> <button class="btn btn-primary" v-bind="data.attrs" v-on="listeners" > <slot/> </button> </template>
Für diese Komponente gibt Ihnen children
zwei Absatz-Tags, während slots().default
nur das zweite anonyme Absatz-Tag übergibt und slots().foo
das erste benannte Absatz-Tag übergibt. Sie haben sowohl children
als auch slots()
, sodass Sie wählen können, ob die Komponente auf einen Slot-Mechanismus aufmerksam gemacht werden soll, oder children
einfach an andere Komponenten übergeben werden soll, um damit umzugehen.
Vorlagenkompilierung
Es könnte Sie interessieren, dass die Vorlagen von Vue tatsächlich kompiliert wurden Rendering-Funktionen. Dies ist ein Implementierungsdetail und in der Regel kein Grund zur Sorge. Wenn Sie jedoch sehen möchten, wie die Funktionalität der Vorlage zusammengestellt ist, könnte es für Sie sehr interessant sein. Hier ist ein einfaches Beispiel für die Verwendung von Vue.compile
zum spontanen Kompilieren einer Vorlagenzeichenfolge: