Heim > Web-Frontend > View.js > Wie werden von Vue3 berechnete Eigenschaften implementiert? Lassen Sie uns über Umsetzungsprinzipien sprechen

Wie werden von Vue3 berechnete Eigenschaften implementiert? Lassen Sie uns über Umsetzungsprinzipien sprechen

青灯夜游
Freigeben: 2022-04-13 21:02:48
nach vorne
4015 Leute haben es durchsucht

Wie werden von Vue3 berechnete Eigenschaften implementiert? Der folgende Artikel gibt Ihnen eine kurze Analyse der Implementierungsprinzipien berechneter Eigenschaften in Vue3.0. Ich hoffe, er wird Ihnen hilfreich sein!

Wie werden von Vue3 berechnete Eigenschaften implementiert? Lassen Sie uns über Umsetzungsprinzipien sprechen

Computed Property ist eine sehr praktische API in der Vue.js-Entwicklung. Sie ermöglicht es Benutzern, eine Berechnungsmethode zu definieren und dann einen neuen Wert basierend auf einigen abhängigen reaktiven Daten zu berechnen. Wenn sich Abhängigkeiten ändern, können berechnete Eigenschaften automatisch neu berechnet werden, um neue Werte zu erhalten, sodass sie sehr bequem zu verwenden sind.

In Vue.js 2.x sind Sie meiner Meinung nach bereits mit der Anwendung berechneter Eigenschaften vertraut. Wir können die berechneten Eigenschaften im Komponentenobjekt definieren. Mit Vue.js 3.0 kann die Verwendung von Vue.js 2.x zwar auch in Komponenten verwendet werden, wir können jedoch auch die API für berechnete Eigenschaften allein verwenden.

Berechnete Eigenschaften sind im Wesentlichen Berechnungen von Abhängigkeiten. Warum verwenden wir Funktionen nicht direkt? Wie wird die API zur Berechnung von Eigenschaften in Vue.js 3.0 implementiert? In diesem Artikel wird das Implementierungsprinzip berechneter Attribute analysiert. (Lernvideo-Sharing: vuejs-Tutorial)

Berechnete Eigenschafts-API: berechnet

Vue.js 3.0 bietet eine berechnete Funktion als berechnete Eigenschafts-API. Schauen wir uns zunächst an, wie sie verwendet wird.

Nehmen wir ein einfaches Beispiel:

const count = ref(1) 
const plusOne = computed(() => count.value + 1) 
console.log(plusOne.value) // 2 
plusOne.value++ // error 
count.value++ 
console.log(plusOne.value) // 3
Nach dem Login kopieren

Wie Sie dem Code entnehmen können, verwenden wir zuerst die Ref-API, um eine Anzahl responsiver Objekte zu erstellen, und verwenden dann die berechnete API, um ein weiteres responsives Objekt plusOne zu erstellen, dessen Wert count ist. Wert + 1: Wenn wir count.value ändern, ändert sich plusOne.value automatisch.

Beachten Sie, dass, wenn wir plusOne.value hier direkt ändern, ein Fehler gemeldet wird. Dies liegt daran, dass es sich bei dem, was wir an computed übergeben, um eine Getter-Funktion handelt und wir nur deren Wert abrufen, aber nicht ändern können es direkt.

In der Getter-Funktion berechnen wir den neuen Wert basierend auf dem reaktionsfähigen Objekt neu, weshalb er als berechnete Eigenschaft bezeichnet wird, und dieses reaktionsfähige Objekt ist die Abhängigkeit der berechneten Eigenschaft.

Natürlich möchten wir manchmal auch den Rückgabewert von „computed“ direkt ändern, dann können wir ein Objekt an „computed“ übergeben:

const count = ref(1) 
const plusOne = computed({ 
  get: () => count.value + 1, 
  set: val => { 
    count.value = val - 1 
  } 
}) 
plusOne.value = 1 
console.log(count.value) // 0
Nach dem Login kopieren

In diesem Beispiel können wir in Kombination mit dem obigen Code sehen, dass wir ein Objekt an „computed“ übergeben Funktion Für Objekte mit Getter-Funktionen und Setter-Funktionen gibt die Getter-Funktion wie zuvor count.value + 1 zurück. Bitte beachten Sie, dass die Setter-Funktion ausgelöst wird, wenn wir hier den Wert von plusOne.value ändern Tatsache ist, dass die Setter-Funktion tatsächlich den Abhängigkeitswert count.value des berechneten Attributs gemäß den übergebenen Parametern ändert, denn sobald der abhängige Wert geändert wird, führen wir den Getter erneut aus, wenn wir das berechnete Attribut erhalten Der auf diese Weise ermittelte Wert ändert sich ebenfalls.

Okay, jetzt kennen wir die beiden Möglichkeiten, die berechnete API zu verwenden. Schauen wir uns an, wie sie implementiert wird:

function computed(getterOrOptions) { 
  // getter 函数 
  let getter 
  // setter 函数 
  let setter 
  // 标准化参数 
  if (isFunction(getterOrOptions)) { 
    // 表面传入的是 getter 函数,不能修改计算属性的值 
    getter = getterOrOptions 
    setter = (process.env.NODE_ENV !== 'production') 
      ? () => { 
        console.warn('Write operation failed: computed value is readonly') 
      } 
      : NOOP 
  } 
  else { 
    getter = getterOrOptions.get 
    setter = getterOrOptions.set 
  } 
  // 数据是否脏的 
  let dirty = true 
  // 计算结果 
  let value 
  let computed 
  // 创建副作用函数 
  const runner = effect(getter, { 
    // 延时执行 
    lazy: true, 
    // 标记这是一个 computed effect 用于在 trigger 阶段的优先级排序 
    computed: true, 
    // 调度执行的实现 
    scheduler: () => { 
      if (!dirty) { 
        dirty = true 
        // 派发通知,通知运行访问该计算属性的 activeEffect 
        trigger(computed, "set" /* SET */, 'value') 
      } 
    } 
    }) 
  // 创建 computed 对象 
  computed = { 
    __v_isRef: true, 
    // 暴露 effect 对象以便计算属性可以停止计算 
    effect: runner, 
    get value() { 
      // 计算属性的 getter 
      if (dirty) { 
        // 只有数据为脏的时候才会重新计算 
        value = runner() 
        dirty = false 
      } 
      // 依赖收集,收集运行访问该计算属性的 activeEffect 
      track(computed, "get" /* GET */, 'value') 
      return value 
    }, 
    set value(newValue) { 
      // 计算属性的 setter 
      setter(newValue) 
    } 
  } 
  return computed 
}
Nach dem Login kopieren

Wie Sie dem Code entnehmen können, erledigt der Prozess der berechneten Funktion hauptsächlich drei Dinge: Parameter normalisieren , erstellen Nebeneffektfunktionen und erzeugen berechnete Objekte. Lassen Sie uns diese Schritte im Detail analysieren.

Das erste sind die Normalisierungsparameter. Die berechnete Funktion akzeptiert zwei Arten von Parametern, eine ist eine Getter-Funktion und die andere ist ein Objekt mit Getter- und Setter-Funktionen. Durch Beurteilung des Parametertyps initialisieren wir die in der Funktion definierten Getter- und Setter-Funktionen.

Der nächste Schritt besteht darin, den Nebeneffekt-Funktionsläufer zu erstellen. Durch die interne Berechnung wird durch den Effekt eine Nebeneffektfunktion erstellt, bei der es sich um eine Kapselungsschicht der Getter-Funktion handelt. Darüber hinaus sollten wir hier auf den zweiten Parameter achten, der das Konfigurationsobjekt der Effektfunktion ist. Unter diesen ist „Lazy“ wahr, was bedeutet, dass der von der Effektfunktion zurückgegebene Läufer nicht sofort ausgeführt wird Wird später analysiert. Der Planer bedeutet, dass seine Planung ausgeführt wird. Wir werden ihn auch später analysieren.

Schließlich wird das berechnete Objekt erstellt und zurückgegeben. Dieses Objekt verfügt auch über Getter- und Setter-Funktionen. Wenn auf das berechnete Objekt zugegriffen wird, wird der Getter ausgelöst und dann wird beurteilt, ob es schmutzig ist. Wenn dies der Fall ist, wird der Läufer ausgeführt und dann wird die Abhängigkeitssammlung durchgeführt, wenn wir das berechnete Objekt direkt festlegen. Der Setter wird ausgelöst, d. h. die in der berechneten Funktion definierte Setter-Funktion wird ausgeführt.

Der Funktionsmechanismus berechneter Attribute

Die Logik der berechneten Funktion ist etwas kompliziert, aber das spielt keine Rolle. Wir können ein Beispiel für die Anwendung berechneter berechneter Attribute kombinieren, um den Funktionsmechanismus des Ganzen zu verstehen berechnetes Attribut. Vor der Analyse müssen wir uns zwei wichtige Variablen in der Berechnung merken. Die erste Variable gibt an, ob der Wert eines berechneten Attributs „schmutzig“ ist, und wird verwendet, um zu bestimmen, ob eine Neuberechnung erforderlich ist . das Endergebnis.

Schauen wir uns nun dieses Beispiel an:

<template> 
  <div> 
    {{ plusOne }} 
  </div> 
  <button @click="plus">plus</button> 
</template> 
<script> 
  import { ref, computed } from &#39;vue&#39; 
  export default { 
    setup() { 
      const count = ref(0) 
      const plusOne = computed(() => { 
        return count.value + 1 
      }) 

      function plus() { 
        count.value++ 
      } 
      return { 
        plusOne, 
        plus 
      } 
    } 
  } 
</script>
Nach dem Login kopieren

可以看到,在这个例子中我们利用 computed API 创建了计算属性对象 plusOne,它传入的是一个 getter 函数,为了和后面计算属性对象的 getter 函数区分,我们把它称作 computed getter。另外,组件模板中引用了 plusOne 变量和 plus 函数。

组件渲染阶段会访问 plusOne,也就触发了 plusOne 对象的 getter 函数:

get value() { 
  // 计算属性的 getter 
  if (dirty) { 
    // 只有数据为脏的时候才会重新计算 
    value = runner() 
    dirty = false 
  } 
  // 依赖收集,收集运行访问该计算属性的 activeEffect 
  track(computed, "get" /* GET */, &#39;value&#39;) 
  return value 
}
Nach dem Login kopieren

由于默认 dirty 是 true,所以这个时候会执行 runner 函数,并进一步执行 computed getter,也就是 count.value + 1,因为访问了 count 的值,并且由于 count 也是一个响应式对象,所以就会触发 count 对象的依赖收集过程。

请注意,由于是在 runner 执行的时候访问 count,所以这个时候的 activeEffect 是 runner 函数。runner 函数执行完毕,会把 dirty 设置为 false,并进一步执行 track(computed,"get",'value') 函数做依赖收集,这个时候 runner 已经执行完了,所以 activeEffect 是组件副作用渲染函数。

所以你要特别注意这是两个依赖收集过程:对于 plusOne 来说,它收集的依赖是组件副作用渲染函数;对于 count 来说,它收集的依赖是 plusOne 内部的 runner 函数。

然后当我们点击按钮的时候,会执行 plus 函数,函数内部通过 count.value++ 修改 count 的值,并派发通知。请注意,这里不是直接调用 runner 函数,而是把 runner 作为参数去执行 scheduler 函数。我们来回顾一下 trigger 函数内部对于 effect 函数的执行方式:

const run = (effect) => { 
  // 调度执行 
  if (effect.options.scheduler) { 
    effect.options.scheduler(effect) 
  } 
  else { 
    // 直接运行 
    effect() 
  } 
}
Nach dem Login kopieren

computed API 内部创建副作用函数时,已经配置了 scheduler 函数,如下:

scheduler: () => { 
  if (!dirty) { 
    dirty = true 
    // 派发通知,通知运行访问该计算属性的 activeEffect 
    trigger(computed, "set" /* SET */, &#39;value&#39;) 
  } 
}
Nach dem Login kopieren

它并没有对计算属性求新值,而仅仅是把 dirty 设置为 true,再执行 trigger(computed, "set" , 'value'),去通知执行 plusOne 依赖的组件渲染副作用函数,即触发组件的重新渲染。

在组件重新渲染的时候,会再次访问 plusOne,我们发现这个时候 dirty 为 true,然后会再次执行 computed getter,此时才会执行 count.value + 1 求得新值。这就是虽然组件没有直接访问 count,但是当我们修改 count 的值的时候,组件仍然会重新渲染的原因。

通过下图可以直观的展现上述过程:

Wie werden von Vue3 berechnete Eigenschaften implementiert? Lassen Sie uns über Umsetzungsprinzipien sprechen

通过以上分析,我们可以看出 computed 计算属性有两个特点:

  • 延时计算,只有当我们访问计算属性的时候,它才会真正运行 computed getter 函数计算;

  • 缓存,它的内部会缓存上次的计算结果 value,而且只有 dirty 为 true 时才会重新计算。如果访问计算属性时 dirty 为 false,那么直接返回这个 value。

现在,我们就可以回答开头提的问题了。和单纯使用普通函数相比,计算属性的优势是:只要依赖不变化,就可以使用缓存的 value 而不用每次在渲染组件的时候都执行函数去计算,这是典型的空间换时间的优化思想。

嵌套计算属性

计算属性也支持嵌套,我们可以针对上述例子做个小修改,即不在渲染函数中访问 plusOne,而在另一个计算属性中访问:

const count = ref(0) 
const plusOne = computed(() => { 
  return count.value + 1 
}) 
const plusTwo = computed(() => { 
  return plusOne.value + 1 
}) 
console.log(plusTwo.value)
Nach dem Login kopieren

从代码中可以看到,当我们访问 plusTwo 的时候,过程和前面都差不多,同样也是两个依赖收集的过程。对于 plusOne 来说,它收集的依赖是 plusTwo 内部的 runner 函数;对于 count 来说,它收集的依赖是 plusOne 内部的 runner 函数。

接着当我们修改 count 的值时,它会派发通知,先运行 plusOne 内部的 scheduler 函数,把 plusOne 内部的 dirty 变为 true,然后执行 trigger 函数再次派发通知,接着运行 plusTwo 内部的 scheduler 函数,把 plusTwo 内部的 dirty 设置为 true。

然后当我们再次访问 plusTwo 的值时,发现 dirty 为 true,就会执行 plusTwo 的 computed getter 函数去执行 plusOne.value + 1,进而执行 plusOne 的 computed gette 即 count.value + 1 + 1,求得最终新值 2。

得益于 computed 这种巧妙的设计,无论嵌套多少层计算属性都可以正常工作。

计算属性的执行顺序

我们曾提到计算属性内部创建副作用函数的时候会配置 computed 为 true,标识这是一个 computed effect,用于在 trigger 阶段的优先级排序。我们来回顾一下 trigger 函数执行 effects 的过程:

const add = (effectsToAdd) => { 
  if (effectsToAdd) { 
    effectsToAdd.forEach(effect => { 
      if (effect !== activeEffect || !shouldTrack) { 
        if (effect.options.computed) { 
          computedRunners.add(effect) 
        } 
        else { 
          effects.add(effect) 
        } 
      } 
    }) 
  } 
} 
const run = (effect) => { 
  if (effect.options.scheduler) { 
    effect.options.scheduler(effect) 
  } 
  else { 
    effect() 
  } 
} 
computedRunners.forEach(run) 
effects.forEach(run)
Nach dem Login kopieren

在添加待运行的 effects 的时候,我们会判断每一个 effect 是不是一个 computed effect,如果是的话会添加到 computedRunners 中,在后面运行的时候会优先执行 computedRunners,然后再执行普通的 effects。

那么为什么要这么设计呢?其实是考虑到了一些特殊场景,我们通过一个示例来说明:

import { ref, computed } from &#39;vue&#39; 
import { effect } from &#39;@vue/reactivity&#39; 
const count = ref(0) 
const plusOne = computed(() => { 
  return count.value + 1 
}) 
effect(() => { 
  console.log(plusOne.value + count.value) 
}) 
function plus() { 
  count.value++ 
} 
plus()
Nach dem Login kopieren

这个示例运行后的结果输出:

1 
3 
3
Nach dem Login kopieren

在执行 effect 函数时运行 console.log(plusOne.value + count.value),所以第一次输出 1,此时 count.value 是 0,plusOne.value 是 1。

后面连续输出两次 3 是因为, plusOne 和 count 的依赖都是这个 effect 函数,所以当我们执行 plus 函数修改 count 的值时,会触发并执行这个 effect 函数,因为 plusOne 的 runner 也是 count 的依赖,count 值修改也会执行 plusOne 的 runner,也就会再次执行 plusOne 的依赖即 effect 函数,因此会输出两次。

那么为什么两次都输出 3 呢?这就跟先执行 computed runner 有关。首先,由于 plusOne 的 runner 和 effect 都是 count 的依赖,当我们修改 count 值的时候, plusOne 的 runner 和 effect 都会执行,那么此时执行顺序就很重要了。

这里先执行 plusOne 的 runner,把 plusOne 的 dirty 设置为 true,然后通知它的依赖 effect 执行 plusOne.value + count.value。这个时候,由于 dirty 为 true,就会再次执行 plusOne 的 getter 计算新值,拿到了新值 2, 再加上 1 就得到 3。执行完 plusOne 的 runner 以及依赖更新之后,再去执行 count 的普通effect 依赖,从而去执行 plusOne.value + count.value,这个时候 plusOne dirty 为 false, 直接返回上次的计算结果 2,然后再加 1 就又得到 3。

如果我们把 computed runner 和 effect 的执行顺序换一下会怎样呢?我来告诉你,会输出如下结果:

1 
2 
3
Nach dem Login kopieren

第一次输出 1 很好理解,因为流程是一样的。第二次为什么会输出 2 呢?我们来分析一下,当我们执行 plus 函数修改 count 的值时,会触发 plusOne 的 runner 和 effect 的执行,这一次我们先让 effect 执行 plusOne.value + count.value,那么就会访问 plusOne.value,但由于 plusOne 的 runner 还没执行,所以此时 dirty 为 false,得到的值还是上一次的计算结果 1,然后再加 1 得到 2。

接着再执行 plusOne 的 runner,把 plusOne 的 dirty 设置为 true,然后通知它的依赖 effect 执行 plusOne.value + count.value,这个时候由于 dirty 为 true,就会再次执行 plusOne 的 getter 计算新值,拿到了 2,然后再加上 1 就得到 3。

知道原因后,我们再回过头看例子。因为 effect 函数依赖了 plusOne 和 count,所以 plusOne 先计算会更合理,这就是为什么我们需要让 computed runner 的执行优先于普通的 effect 函数。

总结

本文分析了计算属性的工作机制、计算属性嵌套代码的执行顺序,以及计算属性的两个特点——延时计算和缓存。

本文转载自:https://juejin.cn/post/7085524315063451684

作者:风度前端

(学习视频分享:web前端开发

Das obige ist der detaillierte Inhalt vonWie werden von Vue3 berechnete Eigenschaften implementiert? Lassen Sie uns über Umsetzungsprinzipien sprechen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:juejin.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage