Skip to content

Commit 5824751

Browse files
committed
use inplace update for raw v-repeat
1 parent fdfac33 commit 5824751

File tree

3 files changed

+166
-24
lines changed

3 files changed

+166
-24
lines changed

src/directives/repeat.js

Lines changed: 103 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var _ = require('../util')
2+
var config = require('../config')
23
var isObject = _.isObject
34
var isPlainObject = _.isPlainObject
45
var textParser = require('../parsers/text')
@@ -47,7 +48,8 @@ module.exports = {
4748
this.idKey =
4849
this._checkParam('track-by') ||
4950
this._checkParam('trackby') // 0.11.0 compat
50-
// cache for primitive value instances
51+
this.hasTransition =
52+
this.el.hasAttribute(config.prefix + 'transition')
5153
this.cache = Object.create(null)
5254
},
5355

@@ -96,7 +98,7 @@ module.exports = {
9698
this.template = transclude(this.template)
9799
this._linkFn = compile(this.template, options)
98100
} else {
99-
this._asComponent = true
101+
this.asComponent = true
100102
var tokens = textParser.parse(id)
101103
if (!tokens) { // static component
102104
var Ctor = this.Ctor = options.components[id]
@@ -127,14 +129,27 @@ module.exports = {
127129
* Update.
128130
* This is called whenever the Array mutates.
129131
*
130-
* @param {Array} data
132+
* @param {Array|Number|String} data
131133
*/
132134

133135
update: function (data) {
134-
if (typeof data === 'number') {
136+
data = data || []
137+
var type = typeof data
138+
if (type === 'number') {
135139
data = range(data)
140+
} else if (type === 'string') {
141+
data = _.toArray(data)
136142
}
137-
this.vms = this.diff(data || [], this.vms)
143+
// There are two situations where we have to use the
144+
// more complex but more accurate diff algorithm:
145+
// 1. We are using components with v-repeat - the
146+
// components could have additional state outside
147+
// of v-repeat data.
148+
// 2. We have transitions on the list, which requires
149+
// precise DOM re-positioning.
150+
this.vms = this.asComponent || this.hasTransition
151+
? this.diff(data, this.vms)
152+
: this.inplaceUpdate(data, this.vms)
138153
// update v-ref
139154
if (this.refID) {
140155
this.vm.$[this.refID] = this.vms
@@ -146,6 +161,43 @@ module.exports = {
146161
}
147162
},
148163

164+
/**
165+
* Inplace update that maximally reuses existing vm
166+
* instances and DOM nodes by simply swapping data into
167+
* existing vms.
168+
*
169+
* @param {Array} data
170+
* @param {Array} oldVms
171+
* @return {Array}
172+
*/
173+
174+
inplaceUpdate: function (data, oldVms) {
175+
oldVms = oldVms || []
176+
var vms
177+
var dir = this
178+
var alias = dir.arg
179+
var converted = dir.converted
180+
if (data.length < oldVms.length) {
181+
oldVms.slice(data.length).forEach(function (vm) {
182+
vm.$destroy(true)
183+
})
184+
vms = oldVms.slice(0, data.length)
185+
overwrite(data, vms, alias, converted)
186+
} else if (data.length > oldVms.length) {
187+
var newVms = data.slice(oldVms.length).map(function (data, i) {
188+
var vm = dir.build(data, i + oldVms.length)
189+
vm.$before(dir.ref)
190+
return vm
191+
})
192+
overwrite(data.slice(0, oldVms.length), oldVms, alias, converted)
193+
vms = oldVms.concat(newVms)
194+
} else {
195+
overwrite(data, oldVms, alias, converted)
196+
vms = oldVms
197+
}
198+
return vms
199+
},
200+
149201
/**
150202
* Diff, based on new data and old data, determine the
151203
* minimum amount of DOM manipulations needed to make the
@@ -192,7 +244,7 @@ module.exports = {
192244
}
193245
}
194246
} else { // new instance
195-
vm = this.build(obj, i)
247+
vm = this.build(obj, i, true)
196248
vm._new = true
197249
}
198250
vms[i] = vm
@@ -258,9 +310,10 @@ module.exports = {
258310
*
259311
* @param {Object} data
260312
* @param {Number} index
313+
* @param {Boolean} needCache
261314
*/
262315

263-
build: function (data, index) {
316+
build: function (data, index, needCache) {
264317
var original = data
265318
var meta = { $index: index }
266319
if (this.converted) {
@@ -280,14 +333,19 @@ module.exports = {
280333
var Ctor = this.Ctor || this.resolveCtor(data, meta)
281334
var vm = this.vm.$addChild({
282335
el: templateParser.clone(this.template),
283-
_asComponent: this._asComponent,
336+
_asComponent: this.asComponent,
284337
_linkFn: this._linkFn,
285338
_meta: meta,
286339
data: data,
287340
inherit: this.inherit
288341
}, Ctor)
342+
// flag this instance as a repeat instance
343+
// so that we can skip it in vm._digest
344+
vm._repeat = true
289345
// cache instance
290-
this.cacheVm(raw, vm)
346+
if (needCache) {
347+
this.cacheVm(raw, vm)
348+
}
291349
return vm
292350
},
293351

@@ -328,12 +386,15 @@ module.exports = {
328386
if (this.refID) {
329387
this.vm.$[this.refID] = null
330388
}
389+
var needUncache = this.asComponent || this.hasTransition
331390
if (this.vms) {
332391
var i = this.vms.length
333392
var vm
334393
while (i--) {
335394
vm = this.vms[i]
336-
this.uncacheVm(vm)
395+
if (needUncache) {
396+
this.uncacheVm(vm)
397+
}
337398
vm.$destroy()
338399
}
339400
}
@@ -360,7 +421,7 @@ module.exports = {
360421
if (!cache[id]) {
361422
cache[id] = vm
362423
} else {
363-
_.warn('Duplicate ID in v-repeat: ' + id)
424+
_.warn('Duplicate track-by key in v-repeat: ' + id)
364425
}
365426
} else if (isObject(data)) {
366427
id = this.id
@@ -369,7 +430,8 @@ module.exports = {
369430
data[id] = vm
370431
} else {
371432
_.warn(
372-
'Duplicate objects are not supported in v-repeat.'
433+
'Duplicate objects are not supported in v-repeat ' +
434+
'when using components or transitions.'
373435
)
374436
}
375437
} else {
@@ -501,4 +563,33 @@ function range (n) {
501563
ret[i] = i
502564
}
503565
return ret
566+
}
567+
568+
/**
569+
* Helper function to overwrite new data Array on to
570+
* existing vms. Used in `inplaceUpdate`.
571+
*
572+
* @param {Array} arr
573+
* @param {Array} vms
574+
* @param {String|undefined} alias
575+
* @param {Boolean} converted
576+
*/
577+
578+
function overwrite (arr, vms, alias, converted) {
579+
var vm, data, raw
580+
for (var i = 0, l = arr.length; i < l; i++) {
581+
vm = vms[i]
582+
data = raw = arr[i]
583+
if (converted) {
584+
vm.$key = data.$key
585+
raw = data.$value
586+
}
587+
if (alias) {
588+
vm[alias] = raw
589+
} else if (!isObject(raw)) {
590+
vm.$value = raw
591+
} else {
592+
vm._setData(raw)
593+
}
594+
}
504595
}

src/instance/scope.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ exports._digest = function () {
120120
i = children.length
121121
while (i--) {
122122
var child = children[i]
123-
if (child.$options.inherit) {
123+
if (!child._repeat && child.$options.inherit) {
124124
child._digest()
125125
}
126126
}

test/unit/specs/directives/repeat_spec.js

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,21 +140,58 @@ if (_.inBrowser) {
140140
expect(el.innerHTML).toBe('<div>aaa</div><!--v-repeat-->')
141141
})
142142

143-
it('v-component', function () {
143+
it('v-component', function (done) {
144144
var vm = new Vue({
145145
el: el,
146146
data: {
147-
items: [{a:1}, {a:2}, {a:3}]
147+
items: [{a:1}, {a:2}]
148148
},
149-
template: '<div v-repeat="items" v-component="test"></div>',
149+
template: '<p v-repeat="items" v-component="test"></p>',
150150
components: {
151151
test: {
152-
template: '<p>{{$index}} {{a}}</p>',
152+
template: '<div>{{$index}} {{a}}</div>',
153153
replace: true
154154
}
155155
}
156156
})
157-
expect(el.innerHTML).toBe('<p>0 1</p><p>1 2</p><p>2 3</p><!--v-repeat-->')
157+
assertMutations(vm, el, done)
158+
})
159+
160+
it('v-component with primitive values', function (done) {
161+
var vm = new Vue({
162+
el: el,
163+
data: {
164+
items: [2, 1, 2]
165+
},
166+
template: '<p v-repeat="items" v-component="test"></p>',
167+
components: {
168+
test: {
169+
template: '<div>{{$index}} {{$value}}</div>',
170+
replace: true
171+
}
172+
}
173+
})
174+
assertPrimitiveMutations(vm, el, done)
175+
})
176+
177+
it('v-component with object of objects', function (done) {
178+
var vm = new Vue({
179+
el: el,
180+
data: {
181+
items: {
182+
a: {a:1},
183+
b: {a:2}
184+
}
185+
},
186+
template: '<p v-repeat="items" v-component="test"></p>',
187+
components: {
188+
test: {
189+
template: '<div>{{$index}} {{$key}} {{a}}</div>',
190+
replace: true
191+
}
192+
}
193+
})
194+
assertObjectMutations(vm, el, done)
158195
})
159196

160197
it('custom element component', function () {
@@ -406,11 +443,11 @@ if (_.inBrowser) {
406443

407444
it('track by id', function (done) {
408445

409-
assertTrackBy('<div v-repeat="list" track-by="id">{{msg}}</div>', function () {
410-
assertTrackBy('<div v-repeat="item:list" track-by="id">{{item.msg}}</div>', done)
446+
assertTrackBy('<div v-repeat="list" v-component="test" track-by="id"></div>', '{{msg}}', function () {
447+
assertTrackBy('<div v-repeat="item:list" v-component="test" track-by="id"></div>', '{{item.msg}}', done)
411448
})
412449

413-
function assertTrackBy (template, next) {
450+
function assertTrackBy (template, componentTemplate, next) {
414451
var vm = new Vue({
415452
el: el,
416453
template: template,
@@ -420,6 +457,11 @@ if (_.inBrowser) {
420457
{ id: 2, msg: 'ha' },
421458
{ id: 3, msg: 'ho' }
422459
]
460+
},
461+
components: {
462+
test: {
463+
template: componentTemplate
464+
}
423465
}
424466
})
425467
assertMarkup()
@@ -453,9 +495,12 @@ if (_.inBrowser) {
453495
var obj = {}
454496
var vm = new Vue({
455497
el: el,
456-
template: '<div v-repeat="items"></div>',
498+
template: '<div v-repeat="items" v-component="test"></div>',
457499
data: {
458500
items: [obj, obj]
501+
},
502+
components: {
503+
test: {}
459504
}
460505
})
461506
expect(_.warn).toHaveBeenCalled()
@@ -464,9 +509,12 @@ if (_.inBrowser) {
464509
it('warn duplicate trackby id', function () {
465510
var vm = new Vue({
466511
el: el,
467-
template: '<div v-repeat="items" trackby="id"></div>',
512+
template: '<div v-repeat="items" v-component="test" track-by="id"></div>',
468513
data: {
469514
items: [{id:1}, {id:1}]
515+
},
516+
components: {
517+
test: {}
470518
}
471519
})
472520
expect(_.warn).toHaveBeenCalled()
@@ -502,9 +550,12 @@ if (_.inBrowser) {
502550
it('teardown', function () {
503551
var vm = new Vue({
504552
el: el,
505-
template: '<div v-repeat="items">{{a}}</div>',
553+
template: '<div v-repeat="items" v-component="test"></div>',
506554
data: {
507555
items: [{a:1}, {a:2}]
556+
},
557+
components: {
558+
test: {}
508559
}
509560
})
510561
vm._directives[0].unbind()

0 commit comments

Comments
 (0)