계산된 속성
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)
从上图中可以看出,我们已经实现了描述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.bar
与obj.foo
如何变化,计算属性的值永远都只能是缓存的值value
,如下图所示。
为了解决这个问题,我们需要一种方式,能够在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
去触发收集的副作用函数。而在计算属性中,我们不再需要自动的触发副作用函数。所以自然会想到,能否在这个地方将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++ }🎜

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.bar
및 obj.foo
가 어떻게 변경되더라도 계산된 속성의 값은 항상 캐시된 값 value
일 수 있음을 의미합니다. > 아래 그림과 같습니다. 🎜🎜
obj.bar
또는 obj.foo
의 값이 변경될 때 sumRes.value
를 가져오기 전에 변경하는 방법이 필요합니다. . >dirty
스위치의 값은 true
로 설정됩니다. 이전 지연 로딩에서 영감을 받아 옵션
을 구성하여 이 기능을 구현할 수 있는지 확인하려고 했습니다. 🎜effect(() => { console.log(obj.foo) }, { scheduler(fn) { jobQueue.add(fn) flushJob() } })🎜 리액티브 객체의 전체 프로세스를 떠올려보겠습니다. 리액티브 객체의 데이터가 수정되면
trigger
가 실행되어 수집된 부작용 함수가 트리거됩니다. 계산된 속성에서는 더 이상 부작용 함수를 자동으로 트리거할 필요가 없습니다. 그렇다면 여기서 dirty
를 true
로 설정할 수 있을까?라고 생각하는 것은 당연합니다. 이 아이디어에 따라 먼저 trigger
를 수정합니다. 🎜rrreee🎜이전 아이디어에 따르면, 부작용 함수 fn
의 구성 항목 options
에 scheduler
가 포함되어 있는지 (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
,副作用函数就可以正常被手动触发。效果如下图所示。
到这里为止,计算属性在功能上已经实现完毕了,让我们尝试完善它。在Vue中,当计算属性中的响应式数据被修改时,计算属性值会同步更改,进而重新渲染,并在页面上更新。渲染函数也会执行effect
,为了说明问题,让我们使用effect
简单的模拟一下。
const sumRes = computed(() => obj.foo + obj.bar) effect(() => console.log('sumRes =', sumRes.value))
这里我们的预期是当obj.foo
或obj.bar
改变时,effect
会自动重新执行。这里存在的问题是,正常的effect
嵌套可以被自动触发(这点我们在上一篇博客中已经实现了),但sumRes
包含的effect
仅会在被读取value
时才会被get
触发执行,这就导致响应式数据obj.foo
与obj.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)处我们手动收集了副作用函数,并当响应式数据被修改时,触发它们。
使用微任务优化调度器
在设计调度器时,我们忽略了一个潜在的问题。
还是先来看一个例子:
effect(() => console.log(obj.foo)) for(let i = 0; i < 1e5; i++) { obj.foo++ }
随着响应式数据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 계산 속성을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

Vue.js는 개발 효율성과 사용자 경험을 향상시키기 위해 프론트 엔드 기술 스택과 밀접하게 통합되어 있습니다. 1) 건설 도구 : 모듈 식 개발을 달성하기 위해 웹 팩 및 롤업과 통합. 2) 주 관리 : Vuex와 통합하여 복잡한 응용 프로그램 상태를 관리합니다. 3) 라우팅 : vuerouter와 통합하여 단일 페이지 응용 프로그램 라우팅을 실현합니다. 4) CSS 전 처리기 : 스타일 개발 효율성을 향상시키기 위해 SASS를 지원하고 덜 지원합니다.

Netflix는 React의 구성 요소 설계 및 가상 DOM 메커니즘이 복잡한 인터페이스와 빈번한 업데이트를 효율적으로 처리 할 수 있기 때문에 REACT를 선택했습니다. 1) 구성 요소 기반 설계를 통해 Netflix는 인터페이스를 관리 가능한 위젯으로 분류하여 개발 효율성 및 코드 유지 관리를 향상시킬 수 있습니다. 2) 가상 DOM 메커니즘은 DOM 운영을 최소화하여 Netflix 사용자 인터페이스의 부드러움과 고성능을 보장합니다.

Vue.js는 사용하기 쉽고 강력하기 때문에 개발자에게 사랑을받습니다. 1) 반응 형 데이터 바인딩 시스템은 뷰를 자동으로 업데이트합니다. 2) 구성 요소 시스템은 코드의 재사용 성과 유지 관리를 향상시킵니다. 3) 컴퓨팅 속성 및 청취자는 코드의 가독성과 성능을 향상시킵니다. 4) vuedevtools를 사용하고 콘솔 오류를 확인하는 것이 일반적인 디버깅 기술입니다. 5) 성능 최적화에는 주요 속성, 계산 된 속성 및 유지 구성 요소 사용이 포함됩니다. 6) 모범 사례에는 명확한 구성 요소 이름 지정, 단일 파일 구성 요소 사용 및 수명주기 후크의 합리적인 사용이 포함됩니다.

vue.js는 효율적이고 유지 관리 가능한 프론트 엔드 애플리케이션을 구축하는 데 적합한 점진적인 JavaScript 프레임 워크입니다. 주요 기능은 다음과 같습니다. 1. 응답 데이터 바인딩, 2. 구성 요소 개발, 3. 가상 Dom. 이러한 기능을 통해 vue.js는 개발 프로세스를 단순화하고 응용 프로그램 성능과 유지 관리를 향상시켜 현대 웹 개발에서 매우 인기가 있습니다.

vue.js와 반응은 각각 고유 한 장점과 단점이 있으며, 선택은 프로젝트 요구 사항 및 팀 조건에 따라 다릅니다. 1) vue.js는 단순하고 사용하기 쉬운 소규모 프로젝트 및 초보자에게 적합합니다. 2) REACT는 풍부한 생태계와 구성 요소 설계로 인해 대규모 프로젝트 및 복잡한 UI에 적합합니다.

vue.js는 여러 기능을 통해 사용자 경험을 향상시킵니다. 1. 응답 시스템은 실시간 데이터 피드백을 실현합니다. 2. 구성 요소 개발은 코드 재사용 성을 향상시킵니다. 3. Vuerouter는 원활한 내비게이션을 제공합니다. 4. 동적 데이터 바인딩 및 전환 애니메이션은 상호 작용 효과를 향상시킵니다. 5. 오류 처리 메커니즘은 사용자 피드백을 보장합니다. 6. 성능 최적화 및 모범 사례는 응용 프로그램 성능을 향상시킵니다.

웹 개발에서 vue.js의 역할은 개발 프로세스를 단순화하고 효율성을 향상시키는 점진적인 JavaScript 프레임 워크 역할을하는 것입니다. 1) 개발자는 반응 형 데이터 바인딩 및 구성 요소 개발을 통해 비즈니스 로직에 집중할 수 있습니다. 2) vue.js의 작동 원리는 반응 형 시스템 및 가상 DOM에 의존하여 성능을 최적화합니다. 3) 실제 프로젝트에서는 Vuex를 사용하여 글로벌 상태를 관리하고 데이터 대응 성을 최적화하는 것이 일반적입니다.

vue.js는 2014 년 Yuxi가 출시하여 사용자 인터페이스를 구축하기 위해 진보적 인 JavaScript 프레임 워크입니다. 핵심 장점은 다음과 같습니다. 1. 응답 데이터 바인딩, 데이터 변경의 자동 업데이트보기; 2. 구성 요소 개발, UI는 독립적이고 재사용 가능한 구성 요소로 분할 될 수 있습니다.


핫 AI 도구

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

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

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

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

인기 기사

뜨거운 도구

VSCode Windows 64비트 다운로드
Microsoft에서 출시한 강력한 무료 IDE 편집기

Atom Editor Mac 버전 다운로드
가장 인기 있는 오픈 소스 편집기

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

Dreamweaver Mac版
시각적 웹 개발 도구

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경
