Reaktivitas
Seperti React.js, Vue bersifat reaktif, artinya semua perubahan pada status aplikasi secara otomatis tercermin di DOM. Tetapi tidak seperti React, Vue melacak dependensi pada waktu render dan hanya memperbarui bagian terkait tanpa "perbandingan".
Kunci reaktivitas Vue.js adalah metode
Object.defineProperty
. Ini memungkinkan Anda untuk menentukan metode pengambil / penyetel kustom pada bidang objek dan mencegat setiap akses ke sana:
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'
Dengan ini, kita dapat menentukan kapan properti tertentu sedang diakses, atau kapan berubah, lalu mengevaluasi ulang semua ekspresi dependen setelah properti berubah.
Ekspresi
Vue.js memungkinkan Anda mengikat ekspresi JavaScript ke atribut simpul DOM menggunakan arahan. Misalnya, akan
<div v-text="s.toUpperCase()"></div>
mengatur teks di dalam div menjadi nilai variabel huruf besar
s
.
Pendekatan paling sederhana untuk mengevaluasi string, seperti
s.toUpperCase()
, adalah dengan menggunakan
eval()
. Meskipun eval tidak pernah dianggap sebagai solusi yang aman, kami dapat mencoba membuatnya sedikit lebih baik dengan menggabungkannya dalam sebuah fungsi dan meneruskan dalam konteks global khusus:
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"
Ini sedikit lebih aman daripada yang asli
eval
dan cukup untuk kerangka kerja sederhana yang kami buat.
Proksi
Sekarang kita bisa menggunakan
Object.defineProperty
untuk membungkus setiap properti dari objek data; dapat digunakan
call()
untuk mengevaluasi ekspresi arbitrer dan untuk mengetahui properti mana yang diakses ekspresi secara langsung atau tidak langsung. Kami juga harus dapat menentukan kapan ekspresi harus dievaluasi ulang karena salah satu variabelnya telah berubah:
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.
Arahan
Sekarang kita dapat mengevaluasi ekspresi arbitrer dan melacak ekspresi mana yang akan dievaluasi ketika satu variabel data tertentu berubah. Yang tersisa hanyalah menetapkan ekspresi ke properti tertentu dari simpul DOM dan benar-benar mengubahnya ketika data berubah.
Seperti di Vue.js, kita akan menggunakan atribut khusus seperti
q-on:click
untuk mengikat penangan event,
q-text
untuk mengikat textContent,
q-bind:style
untuk mengikat gaya CSS, dan seterusnya. Saya menggunakan awalan "q-" di sini karena "q" mirip dengan "vue".
Berikut adalah sebagian daftar kemungkinan arahan yang didukung:
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)),
};
Setiap direktif adalah fungsi yang mengambil simpul DOM, nama parameter opsional untuk kasus seperti
q-on:click
(namanya akan menjadi "klik"). Ini juga membutuhkan ekspresi string (
value
) dan objek data untuk digunakan sebagai konteks ekspresi.
Sekarang setelah kita memiliki semua blok penyusun, saatnya merekatkan semuanya!
Hasil akhir
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));
Kerangka kerja seperti Vue.js yang reaktif dalam kualitas terbaiknya. Seberapa berguna itu? Berikut contohnya:
<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});
Menekan satu tombol akan menambah penghitung dan secara otomatis menyegarkan konten
<p>
. Mengklik yang lain menyetel penghitung ke nol dan juga memperbarui teks.
Seperti yang Anda lihat, Vue.js terlihat seperti keajaiban pada pandangan pertama, tetapi di dalamnya sangat sederhana, dan fungsionalitas dasarnya dapat diimplementasikan hanya dalam beberapa baris kode.
Langkah selanjutnya
Jika Anda tertarik mempelajari lebih lanjut tentang Vue.js, coba terapkan "q-if" untuk mengalihkan visibilitas elemen berdasarkan ekspresi, atau "q-each" untuk mengikat daftar duplikat anak (ini akan menjadi latihan yang baik ).
Sumber lengkap untuk kerangka nano Q ada di Github . Jangan ragu untuk berdonasi jika Anda menemukan masalah atau ingin menyarankan perbaikan!
Sebagai kesimpulan, saya harus menyebutkan yang
Object.defineProperty
digunakan di Vue 2 Vue 3 dan pencipta telah beralih ke fasilitas lain yang disediakan ES6, yaitu
Proxy
dan
Reflect
... Proxy memungkinkan Anda untuk memberikan penangan untuk mencegat akses ke properti objek, seperti dalam contoh kami, sementara Reflect memungkinkan Anda untuk mengakses properti objek dari dalam proxy dan menjaga
this
objek tetap utuh (tidak seperti contoh kami dengan defineProperty).
Saya meninggalkan Proxy / Reflect sebagai latihan untuk pembaca, jadi siapa pun yang mengajukan permintaan untuk menggunakannya dengan benar di Q - saya akan dengan senang hati menggabungkannya. Semoga berhasil!
Semoga Anda menikmati artikelnya. Anda dapat mengikuti berita dan berbagi saran di Github , Twitter, atau berlangganan melalui rss .