Rumah >hujung hadapan web >View.js >Cara melaksanakan sifat terkira Vue3
Dokumentasi rasmi Vue3 mempunyai penerangan ini untuk sifat yang dikira:
Untuk sebarang respons yang mengandungi Untuk logik kompleks data formula , kita harus menggunakan sifat yang dikira .
Sifat yang dikira hanya akan dinilai semula apabila kebergantungan reaktif yang berkaitan berubah.
Daripada huraian di atas, keperluan untuk atribut yang dikira boleh difahami dengan jelas Atribut yang dikira mengira data responsif (huraian mesyuarat 1), dan hasil pengiraan hendaklah dicache (mesyuarat. penerangan 2) ). Mari kita laksanakannya satu demi satu, bermula dengan mencipta harta yang dikira menggunakan computed
.
function effect(fn) { // 副作用函数 const effectFn = () => { cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn) fn() effectStack.pop() activeEffect = effectStack[effectStack.length - 1] } effectFn.deps = [] effectFn() } ... const data = { foo: 1, bar: 2 } const obj = new Proxy(data, { // 响应式对象 get(target, key) { track(target, key) return target[key] }, set(target, key, newValue) { target[key] = newValue trigger(target, key) return true } }) ... const sumRes = computed(() => obj.foo + obj.bar) // (1) console.log(sumRes.value)
Pada (1), kami hanya menulis fungsi untuk mengira atribut Untuk merealisasikan fungsi membaca nilai atribut yang dikira melalui sumRes.value
, apabila melaksanakan atribut yang dikira, adalah perlu untuk mengembalikan. objek dan lulus objek get
mencetuskan fungsi kesan sampingan.
function computed(getter) { const effectFn = effect(getter) const obj = { get value() { return effectFn() } } return obj }
Tetapi fungsi ini jelas tidak boleh dilaksanakan Ini kerana apabila kami melaksanakan effect
tadi, kami perlu melaksanakan fungsi kesan sampingan secara langsung tanpa memberikan nilai pulangan. Tanpa nilai pulangan, computed
secara semula jadi tidak boleh mendapatkan hasil pelaksanaan effect
. Oleh itu, apabila menggunakan effect
dalam sifat yang dikira, fungsi kesan sampingan perlu dikembalikan kepada sifat yang dikira, dan sifat yang dikira menentukan masa untuk melaksanakannya, dan bukannya dilaksanakan serta-merta oleh effect
(iaitu, malas melaksanakan ) .
Untuk mencapai ini, anda perlu menambah suis effect
ke lazy
Memandangkan kami mungkin perlu mengkonfigurasi ciri lain untuk effect
pada masa hadapan, kami menggunakan objek options
. untuk merangkum suis ini.
function effect(fn, options = {}) { const effectFn = () => { cleanup(effectFn) activeEffect = effectFn effectStack.push(effectFn) const res = fn() // (1) effectStack.pop() activeEffect = effectStack[effectStack.length - 1] return res // (2) } effectFn.deps = [] effectFn.options = options // (3) if (!options.lazy) { // (4) effectFn() } return effectFn // (5) }
Kami meletakkan suis lazy
di (4), dan fungsi kesan sampingan yang tidak memerlukan pelaksanaan malas juga akan dilaksanakan secara automatik. Hasil daripada fungsi kesan sampingan dikembalikan pada (1) (2) (5) untuk pelaksanaan malas. Pada masa yang sama, options
diturunkan pada (3) untuk memastikan bahawa apabila effect
bersarang, fungsi kesan sampingan juga akan melaksanakan tingkah laku yang diharapkan. Berdasarkan pengubahsuaian effect
di atas, kami menetapkan suis computed
dalam lazy
.
function computed(getter) { const effectFn = effect(getter, { lazy: true }) const obj = { get value() { // (6) return effectFn() } } return obj } const sumRes = computed(() => obj.foo + obj.bar)
Seperti yang dapat dilihat dari rajah di atas, kami telah melaksanakan penerangan 1, iaitu, menggunakan sifat terkira untuk mengira data responsif, apabila nilai data responsif berubah , nilai atribut yang dikira juga akan berubah dengan sewajarnya. Tetapi memerhatikan perkara (6) kod di atas, tidak sukar untuk mencari bahawa tidak kira apa keadaan sekalipun, selagi nilai sumRes.value
dibaca, fungsi kesan sampingan akan dicetuskan, menyebabkan ia dilaksanakan semula mungkin tidak perlu. Jadi seterusnya, kami cuba melaksanakan penerangan 2, menyimpan cache hasil atribut yang dikira.
Mari kita mulakan dengan yang paling mudah Kami menggunakan pembolehubah value
untuk menyimpan cache nilai yang dikira terakhir dan menambah suis dirty
untuk merekodkan sama ada fungsi kesan sampingan perlu dicetuskan semula.
function computed(getter) { let value let dirty = true const effectFn = effect(getter, { lazy: true }) const obj = { get value() { if(dirty) { value = effectFn() dirty = false } return value } } return obj }
Selepas pengubahsuaian, nilai cache akan berkuat kuasa. Tetapi ini mewujudkan BUG yang jelas Apabila nilai dirty
ditetapkan kepada false
, ia tidak lagi boleh ditukar kepada true
Ini bermakna tidak kira bagaimana data responsif obj.bar
dan obj.foo
Berubah, nilai atribut yang dikira sentiasa boleh menjadi nilai cache value
, seperti yang ditunjukkan dalam rajah di bawah.
Untuk menyelesaikan masalah ini, kita memerlukan cara untuk menukar obj.bar apabila nilai obj.foo
atau sumRes.value
berubah nilai suis ditetapkan kepada dirty
. Diilhamkan oleh pemuatan malas sebelum ini, kami cuba melihat sama ada kami boleh mencapai fungsi ini dengan mengkonfigurasi true
. options
const obj = new Proxy(data, { get(target, key) { track(target, key) return target[key] }, set(target, key, newValue) { target[key] = newValue trigger(target, key) return true } })Mari kita ingat keseluruhan proses objek responsif Apabila data dalam objek responsif diubah suai,
dilaksanakan untuk mencetuskan fungsi kesan sampingan yang dikumpul. Dalam sifat yang dikira, kita tidak lagi perlu mencetuskan fungsi kesan sampingan secara automatik. Jadi wajar untuk berfikir, bolehkah saya menggantikan trigger
dengan dirty
di tempat ini? Mengikuti idea ini, kami mula-mula mengubah suai true
. trigger
function trigger(target, key) { const propsMap = objsMap.get(target) if(!propsMap) return const fns = propsMap.get(key) const otherFns = new Set() fns && fns.forEach(fn => { if(fn !== activeEffect) { otherFns.add(fn) } }) otherFns.forEach(fn => { if(fn.options.scheduler) { // (7) fn.options.scheduler() } else { fn() } }) }Mengikut idea sebelumnya, kami menambah pertimbangan di (7 Jika item konfigurasi
fungsi kesan sampingan fn
mengandungi fungsi options
, kami akan melaksanakan scheduler
sebaliknya fungsi kesan sampingan scheduler
. Kami memanggil fn
di sini scheduler
penjadual. Sejajar dengan itu, kami menambah penjadual dalam atribut yang dikira.
function computed(getter) { let value let dirty = true const effectFn = effect(getter, { lazy: true, scheduler() { // (8) dirty = true } }) const obj = { get value() { if(dirty) { value = effectFn() dirty = false } return value } } return obj }
在(8)处我们添加了调度器。添加调度器后,让我们再来串一下整个流程,当响应式数据被修改时,才会执行trigger
函数。由于我们传入了调度器,因此trigger
函数在执行时不再触发副作用函数,转而执行调度器,此时dirty
开关的值变为了true
。当程序再往下执行时,由于dirty
已经变为true
,副作用函数就可以正常被手动触发。效果如下图所示。
到这里为止,计算属性在功能上已经实现完毕了,让我们尝试完善它。在Vue中,当计算属性中的响应式数据被修改时,计算属性值会同步更改,进而重新渲染,并在页面上更新。渲染函数也会执行effect
,为了说明问题,让我们使用effect
简单的模拟一下。
const sumRes = computed(() => obj.foo + obj.bar) effect(() => console.log('sumRes =', sumRes.value))
这里我们的预期是当obj.foo
或obj.bar
改变时,effect
会自动重新执行。这里存在的问题是,正常的effect
嵌套可以被自动触发(这点我们在上一篇博客中已经实现了),但sumRes
包含的effect
仅会在被读取value
时才会被get
触发执行,这就导致响应式数据obj.foo
与obj.bar
无法收集到外部的effect
,收集不到的副作用函数,自然无法被自动触发。
function computed(getter) { let value let dirty = true const effectFn = effect(getter, { lazy: true, scheduler() { dirty = true trigger(obj, 'value') // (9) } }) const obj = { get value() { if(dirty) { value = effectFn() dirty = false } track(obj, 'value') // (10) return value } } return obj }
在(10)处我们手动收集了副作用函数,并当响应式数据被修改时,触发它们。
在设计调度器时,我们忽略了一个潜在的问题。
还是先来看一个例子:
effect(() => console.log(obj.foo)) for(let i = 0; i < 1e5; i++) { obj.foo++ }
随着响应式数据obj.foo
被不停修改,副作用函数也被不断重复执行,在明显的延迟之后,控制台打印出了大量的数据。但在前端的实际开发中,我们往往只关心最终结果,拿到结果显示在页面上。在这种条件下,我们如何避免副作用函数被重复执行呢?
const jobQueue = new Set() // (11) const p = Promise.resolve() // (12) let isFlushing = false // (13) function flushJob() { // (14) if (isFlushing) return isFlushing = true p.then(() => { jobQueue.forEach(job => { job() }) }).finally(() => { isFlushing = false }) }
这里我们的思路是使用微任务队列来进行优化。(11)处我们定义了一个Set
作为任务队列,(12)处我们定义了一个Promise
来使用微任务。精彩的部分来了,我们的思路是将副作用函数放入任务队列中,由于任务队列是基于Set
实现的,因此,重复的副作用函数仅会保留一个,这是第一点。接着,我们执行flushJob()
,这里我们巧妙的设置了一个isFlushing
开关,这个开关保证了被微任务包裹的任务队列在一次事件循环中只会执行一次,而执行的这一次会在宏任务完成之后触发任务队列中所有不重复的副作用函数,从而直接拿到最终结果,这是第二点。按照这个思路,我们在effect
中添加调度器。
effect(() => { console.log(obj.foo) }, { scheduler(fn) { jobQueue.add(fn) flushJob() } })
需要注意的是,浏览器在执行微任务时,会依次处理微任务队列中的所有微任务。因此,微任务在执行时会阻塞页面的渲染。因此,在实践中需避免在微任务队列中放置过于繁重的任务,以免导致性能问题。
Atas ialah kandungan terperinci Cara melaksanakan sifat terkira Vue3. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!