Reaktivität
Wie React.js ist Vue reaktiv, was bedeutet, dass alle Änderungen am Status der Anwendung automatisch im DOM angezeigt werden. Im Gegensatz zu React verfolgt Vue jedoch die Abhängigkeiten beim Rendern und aktualisiert nur verwandte Teile ohne "Vergleiche".
Der Schlüssel zu Vue.js Reaktivität ist die Methode
Object.defineProperty
. Sie können eine benutzerdefinierte Getter / Setter-Methode für ein Objektfeld angeben und jeden Zugriff darauf abfangen:
const obj = {a: 1};
Object.defineProperty(obj, 'a', {
get() { return 42; },
set(val) { console.log('you want to set "a" to', val); }
});
console.log(obj.a); // prints '42'
obj.a = 100; // prints 'you want to set "a" to 100'
Auf diese Weise können wir bestimmen, wann auf eine bestimmte Eigenschaft zugegriffen wird oder wann sie sich ändert, und dann alle abhängigen Ausdrücke neu auswerten, nachdem sich die Eigenschaft geändert hat.
Ausdrücke
Mit Vue.js können Sie einen JavaScript-Ausdruck mithilfe einer Direktive an ein DOM-Knotenattribut binden. Setzt beispielsweise
<div v-text="s.toUpperCase()"></div>
den Text innerhalb des div auf einen variablen Wert in Großbuchstaben
s
.
Der einfachste Ansatz zum Auswerten von Zeichenfolgen, z. B.
s.toUpperCase()
, ist die Verwendung
eval()
. Obwohl eval nie als sichere Lösung angesehen wurde, können wir versuchen, es ein wenig besser zu machen, indem wir es in eine Funktion einschließen und in einem benutzerdefinierten globalen Kontext übergeben:
const call = (expr, ctx) =>
new Function(`with(this){${`return ${expr}`}}`).bind(ctx)();
call('2+3', null); // returns 5
call('a+1', {a:42}); // returns 43
call('s.toUpperCase()', {s:'hello'}); // returns "HELLO"
Dies ist etwas sicherer als das native
eval
und reicht für das einfache Framework aus, das wir erstellen.
Proxy
Jetzt können wir
Object.defineProperty
jede Eigenschaft des Datenobjekts umbrechen. kann verwendet werden
call()
, um beliebige Ausdrücke auszuwerten und um festzustellen, auf welche Eigenschaften der Ausdruck direkt oder indirekt zugegriffen hat . Wir müssen auch bestimmen können, wann der Ausdruck neu bewertet werden soll, da sich eine seiner Variablen geändert hat:
const data = {a: 1, b: 2, c: 3, d: 'foo'}; // Data model
const vars = {}; // List of variables used by expression
// Wrap data fields into a proxy that monitors all access
for (const name in data) {
let prop = data[name];
Object.defineProperty(data, name, {
get() {
vars[name] = true; // variable has been accessed
return prop;
},
set(val) {
prop = val;
if (vars[name]) {
console.log('Re-evaluate:', name, 'changed');
}
}
});
}
// Call our expression
call('(a+c)*2', data);
console.log(vars); // {"a": true, "c": true} -- these two variables have been accessed
data.a = 5; // Prints "Re-evaluate: a changed"
data.b = 7; // Prints nothing, this variable does not affect the expression
data.c = 11; // Prints "Re-evaluate: c changed"
data.d = 13; // Prints nothing.
Richtlinien
Wir können jetzt beliebige Ausdrücke auswerten und verfolgen, welche Ausdrücke ausgewertet werden sollen, wenn sich eine bestimmte Datenvariable ändert. Sie müssen lediglich bestimmten Eigenschaften des DOM-Knotens Ausdrücke zuweisen und diese tatsächlich ändern, wenn sich die Daten ändern.
Wie in Vue.js verwenden wir spezielle Attribute, z. B.
q-on:click
zum Binden von Ereignishandlern,
q-text
zum Binden von textContent,
q-bind:style
zum Binden des CSS-Stils usw. Ich benutze hier das Präfix "q-", weil "q" ähnlich wie "vue" ist.
Hier ist eine unvollständige Liste möglicher unterstützter Richtlinien:
const directives = {
// Bind innerText to an expression value
text: (el, _, val, ctx) => (el.innerText = call(val, ctx)),
// Bind event listener
on: (el, name, val, ctx) => (el[`on${name}`] = () => call(val, ctx)),
// Bind node attribute to an expression value
bind: (el, name, value, ctx) => el.setAttribute(name, call(value, ctx)),
};
Jede Direktive ist eine Funktion, die einen DOM-Knoten verwendet, einen optionalen Parameternamen für Fälle wie
q-on:click
(der Name lautet "click"). Außerdem müssen eine Ausdruckszeichenfolge (
value
) und ein Datenobjekt als Ausdruckskontext verwendet werden.
Jetzt, da wir alle Bausteine haben, ist es Zeit, alles zusammenzukleben!
Endergebnis
const call = .... // Our "safe" expression evaluator
const directives = .... // Our supported directives
// Currently evaluated directive, proxy uses it as a dependency
// of the individual variables accessed during directive evaluation
let $dep;
// A function to iterate over DOM node and its child nodes, scanning all
// attributes and binding them as directives if needed
const walk = (node, q) => {
// Iterate node attributes
for (const {name, value} of node.attributes) {
if (name.startsWith('q-')) {
const [directive, event] = name.substring(2).split(':');
const d = directives[directive];
// Set $dep to re-evaluate this directive
$dep = () => d(node, event, value, q);
// Evaluate directive for the first time
$dep();
// And clear $dep after we are done
$dep = undefined;
}
}
// Walk through child nodes
for (const child of node.children) {
walk(child, q);
}
};
// Proxy uses Object.defineProperty to intercept access to
// all `q` data object properties.
const proxy = q => {
const deps = {}; // Dependent directives of the given data object
for (const name in q) {
deps[name] = []; // Dependent directives of the given property
let prop = q[name];
Object.defineProperty(q, name, {
get() {
if ($dep) {
// Property has been accessed.
// Add current directive to the dependency list.
deps[name].push($dep);
}
return prop;
},
set(value) { prop = value; },
});
}
return q;
};
// Main entry point: apply data object "q" to the DOM tree at root "el".
const Q = (el, q) => walk(el, proxy(q));
Ein reaktives, Vue.js-ähnliches Framework vom Feinsten. Wie nützlich ist es? Hier ist ein Beispiel:
<div id="counter">
<button q-on:click="clicks++">Click me</button>
<button q-on:click="clicks=0">Reset</button>
<p q-text="`Clicked ${clicks} times`"></p>
</div>
Q(counter, {clicks: 0});
Durch Drücken einer Taste wird der Zähler erhöht und der Inhalt automatisch aktualisiert
<p>
. Wenn Sie auf einen anderen klicken, wird der Zähler auf Null gesetzt und der Text aktualisiert.
Wie Sie sehen können, sieht Vue.js auf den ersten Blick wie Magie aus, ist jedoch sehr einfach und die grundlegende Funktionalität kann in nur wenigen Codezeilen implementiert werden.
Weitere Schritte
Wenn Sie mehr über Vue.js erfahren möchten, versuchen Sie, "q-if" zu implementieren, um die Sichtbarkeit von Elementen basierend auf einem Ausdruck umzuschalten, oder "q-each", um Listen doppelter untergeordneter Kinder zu binden (dies wäre eine gute Übung ).
Die vollständige Quelle für das Q-Nanoframework ist Github . Sie können gerne spenden, wenn Sie ein Problem entdecken oder eine Verbesserung vorschlagen möchten!
Abschließend sollte ich erwähnen, dass
Object.defineProperty
im Vue 2 Vue 3 verwendet wurde und die Entwickler zu einer anderen Einrichtung gewechselt sind, die ES6 zur Verfügung stellt, nämlich
Proxy
und
Reflect
... Mit Proxy können Sie einen Handler übergeben, um den Zugriff auf Objekteigenschaften abzufangen, wie in unserem Beispiel, während Sie mit Reflect über den Proxy auf Objekteigenschaften zugreifen und das
this
Objekt intakt halten können (im Gegensatz zu unserem Beispiel mit defineProperty).
Ich überlasse beide Proxy / Reflect als Übung für den Leser. Wer also eine Anfrage zieht, um sie in Q richtig zu verwenden - ich werde das gerne kombinieren. Viel Glück!
Ich hoffe, Ihnen hat der Artikel gefallen. Sie können den Nachrichten folgen und Vorschläge auf Github , Twitter oder über RSS abonnieren .