목차
계산된 속성
使用微任务优化调度器
웹 프론트엔드 View.js Vue3 계산 속성을 구현하는 방법

Vue3 계산 속성을 구현하는 방법

May 26, 2023 pm 06:36 PM
vue3

계산된 속성

Vue3의 공식 문서에는 계산된 속성에 대한 설명이 있습니다:

  • 반응형 데이터를 포함하는 복잡한 로직의 경우 계산된 속성을 사용해야 합니다.

  • 계산된 속성은 관련 반응 종속성이 변경될 때만 재평가됩니다.

위 설명에서 계산된 속성에 대한 요구 사항을 명확하게 볼 수 있습니다. 계산된 속성은 반응형 데이터를 계산하고(설명 1 충족) 계산 결과를 캐시해야 합니다(설명 2 충족). computed를 사용하여 계산된 속성을 생성하는 것부터 시작해 하나씩 구현해 보겠습니다. 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)
로그인 후 복사

在(1)处,我们简单写了一个计算属性的功能,为了实现通过sumRes.value读取计算属性值功能,在实现计算属性时,需要返回一个对象,通过对象内的get触发副作用函数。

function computed(getter) {
  const effectFn = effect(getter)
  const obj = {
    get value() {
      return effectFn()
    }
  }
  return obj
}
로그인 후 복사

但这个函数显然是无法执行的,这是因为前面我们在实现effect时,需要直接执行副作用函数,不需要提供返回值。没有返回值,computed自然无法获取到effect的执行结果。因此,当在计算属性中使用effect时,需要将副作用函数返回给计算属性,由计算属性决定何时执行,而不再由effect立即执行(即懒执行)。

为了实现这点,就需要向effect中添加一个开关lazy,考虑到我们可能将来还需要对effect配置其它特性,我们使用一个对象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)
}
로그인 후 복사

我们在(4)处放置了lazy开关,不需要懒执行的副作用函数同样会自动执行。在(1)(2)(5)处返回了副作用函数的结果,供懒执行使用。同时在(3)处向下传递了options,保证在effect发生嵌套时,也使得副作用函数执行预期的行为。基于上述effect的修改,我们在computed中设置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)
로그인 후 복사

Vue3 계산 속성을 구현하는 방법

从上图中可以看出,我们已经实现了描述1,即使用计算属性进行响应式数据的计算,当响应式数据的值发生变化时,计算属性的值也会随之改变。但观察上文代码的(6)处,不难发现,无论什么情况下,只要读取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
}
로그인 후 복사

修改之后,缓存的值就能生效了。但这样做产生了一个明显的BUG,当dirty的值被置为false时,无法再变为true,这也就意味着,无论响应式数据obj.barobj.foo如何变化,计算属性的值永远都只能是缓存的值value,如下图所示。

Vue3 계산 속성을 구현하는 방법

为了解决这个问题,我们需要一种方式,能够在obj.barobj.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去触发收集的副作用函数。而在计算属性中,我们不再需要自动的触发副作用函数。所以自然会想到,能否在这个地方将dirty置为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()
    }
  })
}
로그인 후 복사

按照前文的思路,我们在(7)处增加了一个判断,如果副作用函数fn的配置项options中含有scheduler函数,我们就执行scheduler而非副作用函数fn。我们称这里的scheduler

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
}
로그인 후 복사
로그인 후 복사
(1)에서는 단순히 속성을 계산하는 함수를 작성했는데, sumRes.value를 통해 계산된 속성값을 읽어오는 기능을 구현하기 위해 계산된 속성을 구현할 때 객체를 구현했습니다. 개체 내에서 get을 통해 부작용 함수를 트리거해야 합니다.
const sumRes = computed(() => obj.foo + obj.bar)
effect(() => console.log('sumRes =', sumRes.value))
로그인 후 복사
로그인 후 복사

하지만 이 함수는 분명히 실행될 수 없습니다. 왜냐하면 앞서 효과를 구현했을 때 반환 값을 제공하지 않고 부작용 함수를 직접 실행해야 했기 때문입니다. 반환 값이 없으면 computed는 당연히 효과의 실행 결과를 얻을 수 없습니다. 따라서 계산된 속성에 효과를 사용할 경우 부작용 함수를 계산된 속성에 반환해야 하며, 계산된 속성은 효과에 의해 즉시 실행되는 것이 아니라 언제 실행할지 결정합니다. (즉 🎜지연 실행🎜)입니다. 🎜🎜이를 달성하려면 효과에 대한 다른 기능을 구성해야 할 수도 있다는 점을 고려하여 효과lazy 스위치를 추가해야 합니다. 앞으로는 options 개체를 사용하여 이 스위치를 캡슐화합니다. 🎜
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
}
로그인 후 복사
로그인 후 복사
🎜(4)에 lazy 스위치를 배치했는데, 지연 실행이 필요하지 않은 부작용 기능도 자동으로 실행됩니다. 지연 실행을 위해 부작용 함수의 결과가 (1)(2)(5)에 반환됩니다. 동시에 options는 (3)에서 전달되어 효과가 중첩될 때 부작용 함수도 예상된 동작을 수행하도록 합니다. 위의 효과 수정을 기반으로 computed에서 lazy 스위치를 설정했습니다. 🎜
effect(() => console.log(obj.foo))
for(let i = 0; i < 1e5; i++) {
  obj.foo++
}
로그인 후 복사
로그인 후 복사
🎜Vue3 계산 속성 구현 방법🎜🎜에서 확인할 수 있습니다. 위 그림에서는 설명 1, 즉 계산된 속성을 사용하여 반응형 데이터의 값이 변경되면 계산된 속성의 값도 그에 따라 변경되는 것을 볼 수 있습니다. 하지만 위 코드의 (6)번을 보면 상황에 관계없이 sumRes.value의 값만 읽으면 부작용 함수가 발생한다는 것을 어렵지 않게 알 수 있습니다. , 다시 실행할 필요가 없습니다. 다음으로 계산된 속성의 결과를 캐싱하는 설명 2를 구현하려고 합니다. 🎜🎜 가장 간단한 것부터 시작해 보겠습니다. 변수 value를 사용하여 마지막으로 계산된 값을 캐시하고 dirty 스위치를 추가하여 부작용 기능이 필요한지 기록합니다. 다시 트리거되었습니다. 🎜
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
  })
}
로그인 후 복사
로그인 후 복사
🎜수정 후에는 캐시된 값이 적용됩니다. 그러나 이는 명백한 BUG를 생성합니다. dirty 값이 false로 설정되면 더 이상 true로 변경할 수 없습니다. 이는 반응형 데이터 obj.barobj.foo가 어떻게 변경되더라도 계산된 속성의 값은 항상 캐시된 값 value</code일 수 있음을 의미합니다. > 아래 그림과 같습니다. 🎜🎜<img src="/static/imghw/default1.png" data-src="https://img.php.cn/upload/article/000/887/227/168509736457213.png" class="lazy" alt="Vue3 계산 속성 구현 방법" />🎜🎜이를 해결하려면 문제는 <code>obj.bar 또는 obj.foo의 값이 변경될 때 sumRes.value를 가져오기 전에 변경하는 방법이 필요합니다. . >dirty 스위치의 값은 true로 설정됩니다. 이전 지연 로딩에서 영감을 받아 옵션을 구성하여 이 기능을 구현할 수 있는지 확인하려고 했습니다. 🎜
effect(() => { console.log(obj.foo) }, {
  scheduler(fn) {
    jobQueue.add(fn)
    flushJob()
  }
})
로그인 후 복사
로그인 후 복사
🎜 리액티브 객체의 전체 프로세스를 떠올려보겠습니다. 리액티브 객체의 데이터가 수정되면 trigger가 실행되어 수집된 부작용 함수가 트리거됩니다. 계산된 속성에서는 더 이상 부작용 함수를 자동으로 트리거할 필요가 없습니다. 그렇다면 여기서 dirtytrue로 설정할 수 있을까?라고 생각하는 것은 당연합니다. 이 아이디어에 따라 먼저 trigger를 수정합니다. 🎜rrreee🎜이전 아이디어에 따르면, 부작용 함수 fn의 구성 항목 optionsscheduler가 포함되어 있는지 (7)에 판단을 추가했습니다. code >함수에서는 부작용 함수 fn 대신 scheduler를 실행합니다. 여기서는 scheduler를 🎜scheduler🎜라고 부릅니다. 이에 따라 계산된 속성에 스케줄러를 추가합니다. 🎜
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

vue3 프로젝트에서tinymce를 사용하는 방법 vue3 프로젝트에서tinymce를 사용하는 방법 May 19, 2023 pm 08:40 PM

tinymce는 완전한 기능을 갖춘 리치 텍스트 편집기 플러그인이지만,tinymce를 vue에 도입하는 것은 다른 Vue 리치 텍스트 플러그인만큼 원활하지 않습니다.tinymce 자체는 Vue에 적합하지 않으며 @tinymce/tinymce-vue를 도입해야 합니다. 외국 서식 있는 텍스트 플러그인이며 중국어 버전을 통과하지 못했습니다. 공식 웹사이트에서 번역 패키지를 다운로드해야 합니다(방화벽을 우회해야 할 수도 있음). 1. 관련 종속성을 설치합니다. npminstalltinymce-Snpminstall@tinymce/tinymce-vue-S2. 중국어 패키지를 다운로드합니다. 3. 프로젝트 공용 폴더에 스킨과 중국어 패키지를 새로 만들고 다운로드합니다.

vue3+vite: src에서 이미지를 동적으로 가져오기 위해 require를 사용할 때 오류를 해결하는 방법 vue3+vite: src에서 이미지를 동적으로 가져오기 위해 require를 사용할 때 오류를 해결하는 방법 May 21, 2023 pm 03:16 PM

vue3+vite:src는 require를 사용하여 이미지를 동적으로 가져오고 vue3+vite는 여러 이미지를 동적으로 가져옵니다. vue3을 사용하는 경우 require는 이미지를 사용할 수 없습니다. imgUrl:require(' .../assets/test.png') 와 같은 vue2는 typescript가 require를 지원하지 않기 때문에 가져오므로 이를 해결하는 방법은 다음과 같습니다. waitimport를 사용합니다.

Vue3에서 페이지의 일부 콘텐츠를 새로 고치는 방법 Vue3에서 페이지의 일부 콘텐츠를 새로 고치는 방법 May 26, 2023 pm 05:31 PM

페이지를 부분적으로 새로 고치려면 로컬 구성 요소(dom)의 다시 렌더링만 구현하면 됩니다. Vue에서 이 효과를 얻는 가장 쉬운 방법은 v-if 지시어를 사용하는 것입니다. Vue2에서는 v-if 명령을 사용하여 로컬 DOM을 다시 렌더링하는 것 외에도 새 빈 구성 요소를 만들 수도 있습니다. 로컬 페이지를 새로 고쳐야 할 경우 이 빈 구성 요소 페이지로 점프한 다음 다시 돌아올 수 있습니다. 빈 원본 페이지의 beforeRouteEnter 가드. 아래 그림과 같이 Vue3.X에서 새로 고침 버튼을 클릭하여 빨간색 상자 안에 DOM을 다시 로드하고 해당 로딩 상태를 표시하는 방법입니다. Vue3.X의 scriptsetup 구문에 있는 구성 요소의 가드에는

Vue3가 마크다운을 구문 분석하고 코드 강조 표시를 구현하는 방법 Vue3가 마크다운을 구문 분석하고 코드 강조 표시를 구현하는 방법 May 20, 2023 pm 04:16 PM

Vue로 블로그 프론트엔드를 구현하려면 마크다운 파싱을 구현해야 합니다. 코드가 있는 경우 코드 하이라이팅을 구현해야 합니다. markdown-it, vue-markdown-loader,marked,vue-markdown 등과 같은 Vue용 마크다운 구문 분석 라이브러리가 많이 있습니다. 이 라이브러리는 모두 매우 유사합니다. 여기서는 Marked가 사용되었고, 코드 하이라이팅 라이브러리로 하이라이트.js가 사용되었습니다. 구체적인 구현 단계는 다음과 같습니다. 1. 종속 라이브러리를 설치합니다. vue 프로젝트에서 명령 창을 열고 다음 명령 npminstallmarked-save//marked를 입력하여 markdown을 htmlnpmins로 변환합니다.

vue3+ts+axios+pinia를 사용하여 무의미한 새로 고침을 달성하는 방법 vue3+ts+axios+pinia를 사용하여 무의미한 새로 고침을 달성하는 방법 May 25, 2023 pm 03:37 PM

vue3+ts+axios+pinia는 무의미한 새로 고침을 실현합니다. 1. 먼저 프로젝트에서 aiXos 및 pinianpmipinia를 다운로드합니다--savenpminstallaxios--save2. AxiosResponse}from"axios";importaxiosfrom'axios';import{ElMess

Vue3 재사용 가능한 구성 요소를 사용하는 방법 Vue3 재사용 가능한 구성 요소를 사용하는 방법 May 20, 2023 pm 07:25 PM

머리말 Vue든 React든, 여러 개의 반복되는 코드를 접하게 되면, 파일을 중복된 코드 덩어리로 채우는 대신, 이러한 코드를 어떻게 재사용할 수 있을지 고민해 보겠습니다. 실제로 vue와 React 모두 컴포넌트를 추출하여 재사용할 수 있지만, 작은 코드 조각이 발견되어 다른 파일을 추출하고 싶지 않은 경우, 이에 비해 React는 동일한에서 사용할 수 있습니다. 파일에서 해당 위젯을 선언합니다. 또는 다음과 같은 renderfunction을 통해 구현합니다. constDemo:FC=({msg})=>{returndemomsgis{msg}}constApp:FC=()=>{return(

Vue3에서 아바타를 선택하고 자르는 방법 Vue3에서 아바타를 선택하고 자르는 방법 May 29, 2023 am 10:22 AM

최종 효과는 VueCropper 컴포넌트 Yarnaddvue-cropper@next를 설치하는 것입니다. 위의 설치 값은 Vue2이거나 다른 방법을 사용하여 참조하려는 경우 공식 npm 주소: 공식 튜토리얼을 방문하세요. 컴포넌트에서 참조하고 사용하는 것도 매우 간단합니다. 여기서는 해당 컴포넌트와 해당 스타일 파일을 소개하기만 하면 됩니다. 여기서는 import{userInfoByRequest}from'../js/api만 소개하면 됩니다. 내 구성 요소 파일에서 import{VueCropper}from'vue-cropper&

vue3 프로젝트가 패키징되어 서버에 게시된 후 액세스 페이지가 공백으로 표시되는 문제를 해결하는 방법 vue3 프로젝트가 패키징되어 서버에 게시된 후 액세스 페이지가 공백으로 표시되는 문제를 해결하는 방법 May 17, 2023 am 08:19 AM

vue3 프로젝트가 패키징되어 서버에 게시되면 액세스 페이지에 공백 1이 표시됩니다. vue.config.js 파일의 publicPath는 다음과 같이 처리됩니다. const{defineConfig}=require('@vue/cli-service') module.exports=defineConfig({publicPath :process.env.NODE_ENV==='생산'?'./':'/&

See all articles