Rumah >hujung hadapan web >View.js >Cara melaksanakan sifat terkira Vue3

Cara melaksanakan sifat terkira Vue3

WBOY
WBOYke hadapan
2023-05-26 18:36:021833semak imbas

Sifat yang dikira

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)

Cara melaksanakan sifat terkira Vue3

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.

Cara melaksanakan sifat terkira Vue3

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 schedulerpenjadual. 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,副作用函数就可以正常被手动触发。效果如下图所示。

Cara melaksanakan sifat terkira Vue3

到这里为止,计算属性在功能上已经实现完毕了,让我们尝试完善它。在Vue中,当计算属性中的响应式数据被修改时,计算属性值会同步更改,进而重新渲染,并在页面上更新。渲染函数也会执行effect,为了说明问题,让我们使用effect简单的模拟一下。

const sumRes = computed(() => obj.foo + obj.bar)
effect(() => console.log('sumRes =', sumRes.value))

这里我们的预期是当obj.fooobj.bar改变时,effect会自动重新执行。这里存在的问题是,正常的effect嵌套可以被自动触发(这点我们在上一篇博客中已经实现了),但sumRes包含的effect仅会在被读取value时才会被get触发执行,这就导致响应式数据obj.fooobj.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)处我们手动收集了副作用函数,并当响应式数据被修改时,触发它们。

Cara melaksanakan sifat terkira Vue3

使用微任务优化调度器

在设计调度器时,我们忽略了一个潜在的问题。

还是先来看一个例子:

effect(() => console.log(obj.foo))
for(let i = 0; i < 1e5; i++) {
  obj.foo++
}

Cara melaksanakan sifat terkira Vue3

随着响应式数据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()
  }
})

Cara melaksanakan sifat terkira Vue3

需要注意的是,浏览器在执行微任务时,会依次处理微任务队列中的所有微任务。因此,微任务在执行时会阻塞页面的渲染。因此,在实践中需避免在微任务队列中放置过于繁重的任务,以免导致性能问题。

Atas ialah kandungan terperinci Cara melaksanakan sifat terkira Vue3. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam