ホームページ >ウェブフロントエンド >Vue.js >Vue3 計算プロパティを実装する方法

Vue3 計算プロパティを実装する方法

WBOY
WBOY転載
2023-05-26 18:36:021832ブラウズ

計算されたプロパティ

Vue3 の公式ドキュメントには、計算されたプロパティについて次の説明があります:

  • ## を含む応答について# 数式データ の複雑なロジックの場合は、計算されたプロパティ を使用する必要があります。

  • 計算されたプロパティは、

    関連するリアクティブな依存関係が変更された場合にのみ再評価されます。

  • 上記の説明から、計算属性の要件が明確に理解できます。
計算属性は応答データを計算し (説明 1 を満たす)、計算結果はキャッシュされる必要があります (説明 1 を満たす)。説明 2) )

。これを 1 つずつ実装してみましょう。まず computed を使用して計算プロパティを作成します。 <pre class="brush:js;">function effect(fn) { // 副作用函数 const effectFn = () =&gt; { 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(() =&gt; obj.foo + obj.bar) // (1) console.log(sumRes.value)</pre>(1)では単純に属性を計算する関数を記述しましたが、計算された属性の実装時に、計算​​された属性値を

sumRes.value

を通じて読み取る機能を実現するために、 、オブジェクト内の get を通じて副作用関数をトリガーするオブジェクトを返す必要があります。 <pre class="brush:js;">function computed(getter) { const effectFn = effect(getter) const obj = { get value() { return effectFn() } } return obj }</pre>しかし、この関数は明らかに実行できません。これは、以前に

effect

を実装したときに、戻り値を提供せずに副作用関数を直接実行する必要があったためです。戻り値がなければ、computed は当然 effect の実行結果を取得できません。したがって、計算プロパティで effect を使用する場合、副作用関数は計算プロパティに返される必要があり、effect# によってすぐに実行されるのではなく、計算プロパティによっていつ実行されるかが決定されます。 ## (つまり 遅延実行)。 これを実現するには、他の effect

を設定する必要がある可能性があることを考慮して、スイッチ

lazyeffect に追加する必要があります。将来の機能では、オブジェクト options を使用してこのスイッチをカプセル化します。

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)
}
lazy

スイッチを (4) に配置しました。これにより、遅延実行を必要としない副作用関数も自動的に実行されます。遅延実行の場合、副作用関数の結果は (1) (2) (5) で返されます。同時に、

options が (3) で渡され、effect のネストが発生したときに副作用関数も期待どおりの動作を実行するようになります。上記の effect の変更に基づいて、computedlazy スイッチを設定します。

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)

上の図からわかるように、説明 1 を実装しました。つまり、レスポンシブ データの値が変化したときに、計算されたプロパティを使用してレスポンシブ データを計算します。に応じて、計算された属性の値も変化します。しかし、上記のコードのポイント (6) を見ると、Vue3 計算プロパティを実装する方法sumRes.value

の値が読み取られている限り、どのような状況であっても副作用関数がトリガーされることを見つけるのは難しくありません。不必要な可能性のある実装をやり直すことになります。次に、計算された属性の結果をキャッシュする記述 2 を実装してみます。

最も単純なものから始めましょう。変数 value

を使用して最後に計算された値をキャッシュし、

dirty スイッチを追加して副作用が必要かどうかを記録します。機能が再トリガーされます。

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
}
変更後、キャッシュされた値が有効になります。ただし、これには明らかなバグが発生します。dirty

の値が

false に設定されている場合、値を true に変更することはできません。応答データ obj.barobj.foo が変化すると、次の図に示すように、計算された属性の値は常にキャッシュされた値 value になります。 。

この問題を解決するには、Vue3 計算プロパティを実装する方法obj.bar

または

obj.foo の値を変更する方法が必要です。 sumRes.value を取得する前に、dirty スイッチの値を 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
  }
})
レスポンシブ オブジェクトの全体のプロセスを思い出してみましょう。レスポンシブ オブジェクト内のデータが変更されると、trigger

が実行され、収集された副作用関数がトリガーされます。計算プロパティでは、副作用関数を自動的にトリガーする必要がなくなりました。したがって、この場所で

dirtytrue に設定してもよいだろうかと考えるのは自然なことです。このアイデアに従って、まず 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()
    }
  })
}
前回の考え方に従い、副作用関数 fn

の設定項目

optionsscheduler が含まれているかどうかの判定を(7)に追加しました。 関数では、副作用関数 fn の代わりに scheduler を実行します。ここでは schedulerscheduler と呼びます。同様に、計算された属性にスケジューラを追加します。

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

Vue3 計算プロパティを実装する方法

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

const sumRes = computed(() => obj.foo + obj.bar)
effect(() => console.log(&#39;sumRes =&#39;, 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, &#39;value&#39;) // (9)
    } 
  })
  const obj = {
    get value() {
      if(dirty) {
        value = effectFn()
        dirty = false
      }
      track(obj, &#39;value&#39;) // (10)
      return value
    }
  }
  return obj
}

在(10)处我们手动收集了副作用函数,并当响应式数据被修改时,触发它们。

Vue3 計算プロパティを実装する方法

使用微任务优化调度器

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

还是先来看一个例子:

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

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()
  }
})

Vue3 計算プロパティを実装する方法

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

以上がVue3 計算プロパティを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。