Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
forked fromvuejs/vue

Commit531cea5

Browse files
committed
perf: avoid unnecessary re-renders when computed property value did not change
closevuejs#7767
1 parent9084747 commit531cea5

File tree

4 files changed

+153
-51
lines changed

4 files changed

+153
-51
lines changed

‎src/core/instance/state.js‎

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
importconfigfrom'../config'
44
importWatcherfrom'../observer/watcher'
5-
importDep,{pushTarget,popTarget}from'../observer/dep'
5+
import{pushTarget,popTarget}from'../observer/dep'
66
import{isUpdatingChildComponent}from'./lifecycle'
77

88
import{
@@ -164,7 +164,7 @@ export function getData (data: Function, vm: Component): any {
164164
}
165165
}
166166

167-
constcomputedWatcherOptions={lazy:true}
167+
constcomputedWatcherOptions={computed:true}
168168

169169
functioninitComputed(vm:Component,computed:Object){
170170
// $flow-disable-line
@@ -244,13 +244,8 @@ function createComputedGetter (key) {
244244
returnfunctioncomputedGetter(){
245245
constwatcher=this._computedWatchers&&this._computedWatchers[key]
246246
if(watcher){
247-
if(watcher.dirty){
248-
watcher.evaluate()
249-
}
250-
if(Dep.target){
251-
watcher.depend()
252-
}
253-
returnwatcher.value
247+
watcher.depend()
248+
returnwatcher.evaluate()
254249
}
255250
}
256251
}

‎src/core/observer/watcher.js‎

Lines changed: 64 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ export default class Watcher {
2929
id:number;
3030
deep:boolean;
3131
user:boolean;
32-
lazy:boolean;
32+
computed:boolean;
3333
sync:boolean;
3434
dirty:boolean;
3535
active:boolean;
36+
dep:Dep;
3637
deps:Array<Dep>;
3738
newDeps:Array<Dep>;
3839
depIds:SimpleSet;
@@ -56,15 +57,15 @@ export default class Watcher {
5657
if(options){
5758
this.deep=!!options.deep
5859
this.user=!!options.user
59-
this.lazy=!!options.lazy
60+
this.computed=!!options.computed
6061
this.sync=!!options.sync
6162
}else{
62-
this.deep=this.user=this.lazy=this.sync=false
63+
this.deep=this.user=this.computed=this.sync=false
6364
}
6465
this.cb=cb
6566
this.id=++uid// uid for batching
6667
this.active=true
67-
this.dirty=this.lazy// forlazy watchers
68+
this.dirty=this.computed// forcomputed watchers
6869
this.deps=[]
6970
this.newDeps=[]
7071
this.depIds=newSet()
@@ -87,9 +88,12 @@ export default class Watcher {
8788
)
8889
}
8990
}
90-
this.value=this.lazy
91-
?undefined
92-
:this.get()
91+
if(this.computed){
92+
this.value=undefined
93+
this.dep=newDep()
94+
}else{
95+
this.value=this.get()
96+
}
9397
}
9498

9599
/**
@@ -160,8 +164,24 @@ export default class Watcher {
160164
*/
161165
update(){
162166
/* istanbul ignore else */
163-
if(this.lazy){
164-
this.dirty=true
167+
if(this.computed){
168+
// A computed property watcher has two modes: lazy and activated.
169+
// It initializes as lazy by default, and only becomes activated when
170+
// it is depended on by at least one subscriber, which is typically
171+
// another computed property or a component's render function.
172+
if(this.dep.subs.length===0){
173+
// In lazy mode, we don't want to perform computations until necessary,
174+
// so we simply mark the watcher as dirty. The actual computation is
175+
// performed just-in-time in this.evaluate() when the computed property
176+
// is accessed.
177+
this.dirty=true
178+
}else{
179+
// In activated mode, we want to proactively perform the computation
180+
// but only notify our subscribers when the value has indeed changed.
181+
this.getAndInvoke(()=>{
182+
this.dep.notify()
183+
})
184+
}
165185
}elseif(this.sync){
166186
this.run()
167187
}else{
@@ -175,47 +195,54 @@ export default class Watcher {
175195
*/
176196
run(){
177197
if(this.active){
178-
constvalue=this.get()
179-
if(
180-
value!==this.value||
181-
// Deep watchers and watchers on Object/Arrays should fire even
182-
// when the value is the same, because the value may
183-
// have mutated.
184-
isObject(value)||
185-
this.deep
186-
){
187-
// set new value
188-
constoldValue=this.value
189-
this.value=value
190-
if(this.user){
191-
try{
192-
this.cb.call(this.vm,value,oldValue)
193-
}catch(e){
194-
handleError(e,this.vm,`callback for watcher "${this.expression}"`)
195-
}
196-
}else{
197-
this.cb.call(this.vm,value,oldValue)
198+
this.getAndInvoke(this.cb)
199+
}
200+
}
201+
202+
getAndInvoke(cb:Function){
203+
constvalue=this.get()
204+
if(
205+
value!==this.value||
206+
// Deep watchers and watchers on Object/Arrays should fire even
207+
// when the value is the same, because the value may
208+
// have mutated.
209+
isObject(value)||
210+
this.deep
211+
){
212+
// set new value
213+
constoldValue=this.value
214+
this.value=value
215+
this.dirty=false
216+
if(this.user){
217+
try{
218+
cb.call(this.vm,value,oldValue)
219+
}catch(e){
220+
handleError(e,this.vm,`callback for watcher "${this.expression}"`)
198221
}
222+
}else{
223+
cb.call(this.vm,value,oldValue)
199224
}
200225
}
201226
}
202227

203228
/**
204-
* Evaluate the value of the watcher.
205-
* This only gets called forlazy watchers.
229+
* Evaluateand returnthe value of the watcher.
230+
* This only gets called forcomputed property watchers.
206231
*/
207232
evaluate(){
208-
this.value=this.get()
209-
this.dirty=false
233+
if(this.dirty){
234+
this.value=this.get()
235+
this.dirty=false
236+
}
237+
returnthis.value
210238
}
211239

212240
/**
213-
* Depend onall deps collected by this watcher.
241+
* Depend onthis watcher. Only for computed property watchers.
214242
*/
215243
depend(){
216-
leti=this.deps.length
217-
while(i--){
218-
this.deps[i].depend()
244+
if(this.dep&&Dep.target){
245+
this.dep.depend()
219246
}
220247
}
221248

‎test/unit/features/options/computed.spec.js‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,40 @@ describe('Options computed', () => {
216216
})
217217
expect(()=>vm.a).toThrowError('rethrow')
218218
})
219+
220+
// #7767
221+
it('should avoid unnecessary re-renders',done=>{
222+
constcomputedSpy=jasmine.createSpy('computed')
223+
constupdatedSpy=jasmine.createSpy('updated')
224+
constvm=newVue({
225+
data:{
226+
msg:'bar'
227+
},
228+
computed:{
229+
a(){
230+
computedSpy()
231+
returnthis.msg!=='foo'
232+
}
233+
},
234+
template:`<div>{{ a }}</div>`,
235+
updated:updatedSpy
236+
}).$mount()
237+
238+
expect(vm.$el.textContent).toBe('true')
239+
expect(computedSpy.calls.count()).toBe(1)
240+
expect(updatedSpy.calls.count()).toBe(0)
241+
242+
vm.msg='baz'
243+
waitForUpdate(()=>{
244+
expect(vm.$el.textContent).toBe('true')
245+
expect(computedSpy.calls.count()).toBe(2)
246+
expect(updatedSpy.calls.count()).toBe(0)
247+
}).then(()=>{
248+
vm.msg='foo'
249+
}).then(()=>{
250+
expect(vm.$el.textContent).toBe('false')
251+
expect(computedSpy.calls.count()).toBe(3)
252+
expect(updatedSpy.calls.count()).toBe(1)
253+
}).then(done)
254+
})
219255
})

‎test/unit/modules/observer/watcher.spec.js‎

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,26 +144,70 @@ describe('Watcher', () => {
144144
}).then(done)
145145
})
146146

147-
it('lazy mode',done=>{
147+
it('computed mode, lazy',done=>{
148+
letgetterCallCount=0
148149
constwatcher=newWatcher(vm,function(){
150+
getterCallCount++
149151
returnthis.a+this.b.d
150-
},null,{lazy:true})
151-
expect(watcher.lazy).toBe(true)
152+
},null,{computed:true})
153+
154+
expect(getterCallCount).toBe(0)
155+
expect(watcher.computed).toBe(true)
152156
expect(watcher.value).toBeUndefined()
153157
expect(watcher.dirty).toBe(true)
154-
watcher.evaluate()
158+
expect(watcher.dep).toBeTruthy()
159+
160+
constvalue=watcher.evaluate()
161+
expect(getterCallCount).toBe(1)
162+
expect(value).toBe(5)
155163
expect(watcher.value).toBe(5)
156164
expect(watcher.dirty).toBe(false)
165+
166+
// should not get again if not dirty
167+
watcher.evaluate()
168+
expect(getterCallCount).toBe(1)
169+
157170
vm.a=2
158171
waitForUpdate(()=>{
172+
expect(getterCallCount).toBe(1)
159173
expect(watcher.value).toBe(5)
160174
expect(watcher.dirty).toBe(true)
161-
watcher.evaluate()
175+
176+
constvalue=watcher.evaluate()
177+
expect(getterCallCount).toBe(2)
178+
expect(value).toBe(6)
162179
expect(watcher.value).toBe(6)
163180
expect(watcher.dirty).toBe(false)
164181
}).then(done)
165182
})
166183

184+
it('computed mode, activated',done=>{
185+
letgetterCallCount=0
186+
constwatcher=newWatcher(vm,function(){
187+
getterCallCount++
188+
returnthis.a+this.b.d
189+
},null,{computed:true})
190+
191+
// activate by mocking a subscriber
192+
constsubMock=jasmine.createSpyObj('sub',['update'])
193+
watcher.dep.addSub(subMock)
194+
195+
constvalue=watcher.evaluate()
196+
expect(getterCallCount).toBe(1)
197+
expect(value).toBe(5)
198+
199+
vm.a=2
200+
waitForUpdate(()=>{
201+
expect(getterCallCount).toBe(2)
202+
expect(subMock.update).toHaveBeenCalled()
203+
204+
// since already computed, calling evaluate again should not trigger
205+
// getter
206+
watcher.evaluate()
207+
expect(getterCallCount).toBe(2)
208+
}).then(done)
209+
})
210+
167211
it('teardown',done=>{
168212
constwatcher=newWatcher(vm,'b.c',spy)
169213
watcher.teardown()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2026 Movatter.jp