>웹 프론트엔드 >View.js >Vue3 리스너의 구현 원리는 무엇입니까

Vue3 리스너의 구현 원리는 무엇입니까

王林
王林앞으로
2023-05-16 15:04:061433검색

반응형 개체 듣기

앞서 우리는 반응형 데이터의 값을 자동으로 계산하고 캐시할 수 있는 계산된 속성에 대해 이야기했습니다. 그리고 반응형 데이터가 변경될 때 일부 미리 설정된 작업만 수행해야 한다면 watch 리스너를 사용할 수 있습니다. 가장 간단한 예제를 먼저 구현한 후 조금씩 확장해 보겠습니다. watch侦听器。我们还是先来实现一个最简单的例子,然后来一点一点扩充它。

const data = {foo: 1}
const obj = reactive(data)
watch(obj, () => {
  console.log('obj已改变')
})

在这个例子中,我们使用了watch侦听器,当obj的属性被改变时,控制台应该会打印出obj已改变。基于前面我们对计算属性的实现,这里我们已经有了一个大概的思路。把watch视为响应式对象的副作用函数,当响应式对象改变时,触发执行该副作用函数。

想要触发副作用函数,必须先收集它,还记得副作用函数是如何收集的吗?对,当响应式数据被get时,收集副作用函数。所以首先,我们需要让watch被响应式对象收集到。     

function watch(getter, cb) {
  effect(
    () => getter.foo
  )
}

接着,我们还需要让我们预设的方法被执行。当响应式数据被set时,触发副作用函数。这里我们想触发的是cb这个传入的回调函数,这里我们就又能用到实现计算属性时的调度器了,当调度器存在时,set触发的trigger会先执行调度器中的函数。

function watch(getter, cb) {
  effect(
    () => getter.foo,
    {
      scheduler() {
        cb()
      }
    }
  )
}

Vue3 리스너의 구현 원리는 무엇입니까

一个简单的侦听器已经完成了!这里我们为了简单,把功能写死了,仅支持对obj.foo的侦听。接下来,我们就要想想,如何实现对响应式对象的任意属性进行侦听?

按照前面的思路,想要实现对响应式对象的任意属性的侦听,就需要我们get到该对象的每一个属性,这就需要我们对响应式对象进行一次递归遍历。

function traverse(value, seen = new Set()) { // (1)
  if(typeof value !== 'object' || value === null || seen.has(value)) return
  seen.add(value)
  for(const key in value) {
    traverse(value[key], seen)
  }
  return value
}

为了避免递归遍历对象时,循环引用造成的死循环,我们在(1)处创建了Set,当重复出现相同的对象时,直接返回。

侦听属性值

在Vue3中,我们不能直接侦听响应式对象的属性值。如果需要侦听响应式对象的属性值,就需要一个getter函数,让侦听器能被响应式对象收集到。

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  () => {
  console.log('obj.foo已改变')
})

指定了属性就意味着,当前的侦听器仅会被指定的属性触发,就无需递归遍历整个响应式对象了。

function watch(getter, cb) {
  if(typeof getter !== 'function') getter = traverse(getter) // (2)
  effect(
    () => getter(),
    {
      scheduler() {
        cb()
      }
    }
  )
}

Vue3 리스너의 구현 원리는 무엇입니까

在(2)处,我们增加了一个判断,如果传入的已经是getter函数,我们直接使用,如果不是getter函数,则认为是一个响应式对象,就需要进行递归遍历。

侦听获取新值和旧值

在Vue中我们还需要能够在回调函数cb()中拿到响应式数据更新前后的新值与旧值。

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  (newValue, oldValue) => {
  console.log(newValue, oldValue)
})

接下来的问题是,如何获取newValueoldValuenewValue好解决,执行完回调函数cb()得到的就是newValue,但这里如何获取oldValue的值呢?要从watch中拿到旧值,那就不能让副作用函数被立即执行。这里想到了什么?对,在实现计算属性的时候,我们用到过的lazy,它可以禁止副作用函数自动执行。

function watch(getter, cb) {
  if(typeof getter !== 'function') getter = traverse(getter)
  let oldValue
  const effectFn = effect(
    () => getter(),
    {
      lazy: true, // (3)
      scheduler() {
          cb(oldValue)
      }
    }
  )
  oldValue = effectFn() // (4)
}

在(3)处我们设置了lazy开关,设置了lazy后,副作用函数的执行权就交到了我们自己手上。在(4)处,我们手动执行了副作用函数。这里可以需要我们向前回顾一下,前面我们传入的getter是一个函数() => obj.foo,而effect函数的第一个参数就是真正被执行的副作用函数,所以我们手动执行的,其实就是函数() => obj.foo,这样我们就拿到了旧值。

如何获取新值呢?在响应式数据的值更新后,副作用函数effect会被触发执行,当调度器属性存在时,执行调度器。在调度器中,我们可以再次执行副作用函数,通过() => obj.foo拿到改变后的新值。

function watch(getter, cb) {
  if(typeof getter !== 'function') getter = traverse(getter)
  let oldValue, newValue
  const effectFn = effect(
    () => getter(),
    {
      lazy: true,
      scheduler() {
        newValue = effectFn()
        cb(newValue, oldValue)
        oldValue = newValue // (5)
      }
    }
  )
  oldValue = effectFn()
}

在(5)处,执行完回调函数cb(),我们进行了一下善后工作,更新了oldValue的值,为下一次回调做准备。

Vue3 리스너의 구현 원리는 무엇입니까

有时,我们还希望侦听器可以在创建时就立即执行回调函数。

const data = {
  foo: 1
}
const obj = reactive(data)
watch(
  () => obj.foo, 
  (newValue, oldValue) => {
      console.log('newValue:', newValue,', oldValue:', oldValue)
  },
  { immediate: true }
)

immediate的值为true时,需要立即执行。明确了需求,我们来完善watch

function watch(getter, cb, options = {}) {
  if(typeof getter !== 'function') getter = traverse(getter)
  let oldValue, newValue
  function job() { // (6)
    newValue = effectFn()
    cb(newValue, oldValue)
    oldValue = newValue
  }

  const effectFn = effect(
    () => getter(),
    {
      lazy: true,
      scheduler: job,
    }
  )

  if(options.immediate) {  // (7)
    job()
  } else {
    oldValue = effectFn()
  } 
}

이 예에서는 watch 리스너를 사용합니다. obj 속성이 변경되면 콘솔은 obj가 변경되었습니다>를 인쇄해야 합니다. 코드>. 계산된 속성의 이전 구현을 기반으로 여기에 이미 일반적인 아이디어가 있습니다. <code>watch를 반응 객체의 부작용 기능으로 생각하세요. 반응 객체가 변경되면 부작용 기능이 실행됩니다. 🎜🎜부작용 기능을 발동시키려면 먼저 수집해야 합니다. 부작용 기능이 어떻게 수집되는지 기억하시나요? 네, 🎜반응형 데이터가 get이면 부작용 함수🎜가 수집됩니다. 그래서 먼저 반응형 객체가 수집하는 watch를 만들어야 합니다.​ 🎜rrreee🎜다음으로 기본 메서드도 실행되도록 해야 합니다. 반응형 데이터가 설정되면 부작용 기능이 실행됩니다. 여기서 트리거하려는 것은 cb에 전달된 콜백 함수입니다. 여기서는 계산된 속성을 구현할 때 스케줄러를 사용할 수 있습니다. set트리거된 Trigger는 먼저 스케줄러의 기능을 실행합니다. 🎜rrreee🎜Vue3 리스너의 구현 원리는 무엇입니까🎜🎜a 간단한 리스너가 완성되었습니다! 여기서는 단순화를 위해 함수를 완전하게 작성했으며 obj.foo 듣기만 지원합니다. 다음으로 반응형 개체의 속성을 어떻게 수신할지 생각해야 합니다. 🎜🎜이전 아이디어에 따르면, 반응형 객체의 속성을 듣고 싶다면 객체의 모든 속성을 가져와야 해야 하며, 이를 위해서는 반응형 객체 재귀 순회를 수행해야 합니다. 🎜rrreee🎜객체를 재귀적으로 순회할 때 순환 참조로 인해 발생하는 무한 루프를 피하기 위해 (1)Set을 만들었습니다. 동일한 객체가 반복적으로 나타날 때 직접적으로 말이죠. 반품. 🎜🎜속성값 듣기🎜🎜Vue3에서는 반응형 객체의 속성값을 직접 들을 수 없습니다. 반응형 객체의 속성값을 들어야 하는 경우, 반응형 객체가 리스너를 수집할 수 있도록 getter 함수가 필요합니다. 🎜rrreee🎜속성을 지정하면 현재 리스너가 지정된 속성에 의해서만 트리거되며 전체 반응형 개체를 재귀적으로 순회할 필요가 없음을 의미합니다. 🎜rrreee🎜Vue3 리스너의 구현 원리는 무엇입니까🎜🎜at At (2) 전달된 내용이 이미 getter 함수인 경우에는 getter 함수가 아닌 것으로 간주하여 판단을 추가했습니다. 반응형 객체가 되려면 재귀 순회가 필요합니다. 🎜🎜새 값과 이전 값을 얻기 위해 듣기🎜🎜Vue에서는 콜백 함수 cb()에서 반응형 데이터 업데이트 전후에 새 값과 이전 값을 얻을 수도 있어야 합니다. 코드>. 🎜rrreee🎜다음 질문은 <code>newValueoldValue를 얻는 방법입니다. newValue는 콜백 함수 cb()를 실행한 후 newValue를 가져오지만, oldValue를 가져오는 방법은 다음과 같습니다. 가치는 무엇입니까? watch에서 이전 값을 가져오려면 부작용 기능을 즉시 실행할 수 없습니다. 여기서 무엇이 떠오르나요? 예, 계산된 속성을 구현할 때 부작용 함수의 자동 실행을 금지할 수 있는 lazy를 사용했습니다. 🎜rrreee🎜(3)에서 lazy 스위치를 설정했습니다. lazy를 설정한 후 부작용 함수의 실행 권한이 우리에게 넘겨집니다. (4)에서는 부작용 기능을 수동으로 실행합니다. 여기서 다시 살펴봐야 합니다. 앞서 전달한 getter() => obj.foo 함수입니다. 함수의 매개변수는 실제로 실행되는 부작용 함수이므로 수동으로 실행하는 것은 실제로 () => obj.foo 함수이므로 이전 값을 얻습니다. 🎜🎜새로운 가치를 얻는 방법은 무엇인가요? 반응형 데이터의 값이 업데이트된 후 부작용 함수인 효과가 실행되어 스케줄러 속성이 존재하면 스케줄러가 실행됩니다. 스케줄러에서는 Side Effect 함수를 다시 실행하고 () => obj.foo를 통해 변경된 새 값을 가져올 수 있습니다. 🎜rrreee🎜(5)에서 콜백 함수 cb()를 실행한 후 몇 가지 후속 작업을 수행하고 oldValue 값을 업데이트하여 다음 콜백을 준비했습니다. 🎜🎜Vue3 리스너의 구현 원리는 무엇입니까🎜🎜가끔 우리는 또한 리스너가 생성 즉시 콜백 함수를 실행하기를 원합니다. 🎜rrreee🎜immediate 값이 true일 경우 즉시 실행해야 합니다. 요구 사항을 명확히 한 후 watch 리스너를 개선해 보겠습니다. 🎜
function watch(getter, cb, options = {}) {
  if(typeof getter !== &#39;function&#39;) getter = traverse(getter)
  let oldValue, newValue
  function job() { // (6)
    newValue = effectFn()
    cb(newValue, oldValue)
    oldValue = newValue
  }

  const effectFn = effect(
    () => getter(),
    {
      lazy: true,
      scheduler: job,
    }
  )

  if(options.immediate) {  // (7)
    job()
  } else {
    oldValue = effectFn()
  } 
}

在(6)处,我们抽离了回调函数的执行逻辑,当options.immediate存在时,直接触发执行。

Vue3 리스너의 구현 원리는 무엇입니까

实现效果

Vue3 리스너의 구현 원리는 무엇입니까

위 내용은 Vue3 리스너의 구현 원리는 무엇입니까의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제