8000 v-for: no longer pre-convert values into arrays before piping it thro… · codeclever/vue@03b9d41 · GitHub
[go: up one dir, main page]

Skip to content

Commit 03b9d41

Browse files
committed
v-for: no longer pre-convert values into arrays before piping it through filters
1 parent b8971be commit 03b9d41

File tree

5 files changed

+89
-56
lines changed

5 files changed

+89
-56
lines changed

src/directive.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,12 @@ Directive.prototype._bind = function () {
9999
} else {
100100
this._update = noop
101101
}
102-
// pre-process hook called before the value is piped
103-
// through the filters. used in v-for.
104102
var preProcess = this._preProcess
105103
? _.bind(this._preProcess, this)
106104
: null
105+
var postProcess = this._postProcess
106+
? _.bind(this._postProcess, this)
107+
: null
107108
var watcher = this._watcher = new Watcher(
108109
this.vm,
109110
this.expression,
@@ -113,6 +114,7 @@ Directive.prototype._bind = function () {
113114
twoWay: this.twoWay,
114115
deep: this.deep,
115116
preProcess: preProcess,
117+
postProcess: postProcess,
116118
scope: this._scope
117119
}
118120
)

src/directives/public/for.js

Lines changed: 37 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,6 @@ module.exports = {
6161
},
6262

6363
update: function (data) {
64-
if (process.env.NODE_ENV !== 'production' && !_.isArray(data)) {
65-
_.warn(
66-
'v-for pre-converts Objects into Arrays, and ' +
67-
'v-for filters should always return Arrays.'
68-
)
69-
}
7064
this.diff(data)
7165
this.updateRef()
7266
this.updateModel()
@@ -87,25 +81,31 @@ module.exports = {
8781
*/
8882

8983
diff: function (data) {
84+
// check if the Array was converted from an Object
85+
var item = data[0]
86+
var convertedFromObject = this.fromObject =
87+
isObject(item) &&
88+
item.hasOwnProperty('$key') &&
89+
item.hasOwnProperty('$value')
90+
9091
var idKey = this.idKey
91-
var converted = this.converted
9292
var oldFrags = this.frags
9393
var frags = this.frags = new Array(data.length)
9494
var alias = this.alias
9595
var start = this.start
9696
var end = this.end
9797
var inDoc = _.inDoc(start)
9898
var init = !oldFrags
99-
var i, l, frag, item, key, value, primitive
99+
var i, l, frag, key, value, primitive
100100

101101
// First pass, go through the new Array and fill up
102102
// the new frags array. If a piece of data has a cached
103103
// instance for it, we reuse it. Otherwise build a new
104104
// instance.
105105
for (i = 0, l = data.length; i < l; i++) {
106106
item = data[i]
107-
key = converted ? item.$key : null
108-
value = converted ? item.$value : item
107+
key = convertedFromObject ? item.$key : null
108+
value = convertedFromObject ? item.$value : item
109109
primitive = !isObject(value)
110110
frag = !init && this.getCachedFrag(value, i, key)
111111
if (frag) { // reusable fragment
@@ -126,7 +126,7 @@ module.exports = {
126126
}
127127
// update data for track-by, object repeat &
128128
// primitive values.
129-
if (idKey || converted || primitive) {
129+
if (idKey || convertedFromObject || primitive) {
130130
frag.scope[alias] = value
131131
}
132132
} else { // new isntance
@@ -230,7 +230,7 @@ module.exports = {
230230
if (!ref) return
231231
var hash = (this._scope || this.vm).$refs
232232
var refs
233-
if (!this.converted) {
233+
if (!this.fromObject) {
234234
refs = this.frags.map(findVmFromFrag)
235235
} else {
236236
refs = {}
@@ -458,29 +458,28 @@ module.exports = {
458458

459459
/**
460460
* Pre-process the value before piping it through the
461-
* filters, and convert non-Array objects to arrays.
462-
*
463-
* This function will be bound to this directive instance
464-
* and passed into the watcher.
465-
*
466-
* @param {*} value
467-
* @return {Array}
468-
* @private
461+
* filters. This is passed to and called by the watcher.
469462
*/
470463

471464
_preProcess: function (value) {
472465
// regardless of type, store the un-filtered raw value.
473466
this.rawValue = value
474-
var type = this.rawType = typeof value
475-
if (!_.isPlainObject(value)) {
476-
this.converted = false
477-
if (type === 'number') {
478-
value = range(value)
479-
} else if (type === 'string') {
480-
value = _.toArray(value)
481-
}
482-
return value || []
483-
} else {
467+
return value
468+
},
469+
470+
/**
471+
* Post-process the value after it has been piped through
472+
* the filters. This is passed to and called by the watcher.
473+
*
474+
* It is necessary for this to be called during the
475+
* wathcer's dependency collection phase because we want
476+
* the v-for to update when the source Object is mutated.
477+
*/
478+
479+
_postProcess: function (value) {
480+
if (_.isArray(value)) {
481+
return value
482+
} else if (_.isPlainObject(value)) {
484483
// convert plain object to array.
485484
var keys = Object.keys(value)
486485
var i = keys.length
@@ -493,8 +492,15 @@ module.exports = {
493492
$value: value[key]
494493
}
495494
}
496-
this.converted = true
497495
return res
496+
} else {
497+
var type = typeof value
498+
if (type === 'number') {
499+
value = range(value)
500+
} else if (type === 'string') {
501+
value = _.toArray(value)
502+
}
503+
return value || []
498504
}
499505
},
500506

src/filters/array-filters.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
var _ = require('../util')
22
var Path = require('../parsers/path')
3+
var toArray = require('../directives/public/for')._postProcess
34

45
/**
56
* Filter filter for arrays
@@ -10,6 +11,7 @@ var Path = require('../parsers/path')
1011
*/
1112

1213
exports.filterBy = function (arr, search, delimiter /* ...dataKeys */) {
14+
arr = toArray(arr)
1315
if (search == null) {
1416
return arr
1517
}
@@ -25,15 +27,27 @@ exports.filterBy = function (arr, search, delimiter /* ...dataKeys */) {
2527
var keys = _.toArray(arguments, n).reduce(function (prev, cur) {
2628
return prev.concat(cur)
2729
}, [])
28-
return arr.filter(function (item) {
29-
if (keys.length) {
30-
return keys.some(function (key) {
31-
return contains(Path.get(item, key), search)
32-
})
30+
var res = []
31+
var item, key, val, j
32+
for (var i = 0, l = arr.length; i < l; i++) {
33+
item = arr[i]
34+
val = (item && item.$value) || item
35+
j = keys.length
36+
if (j) {
37+
while (j--) {
38+
key = keys[j]
39+
if ((key === '$key' && contains(item.$key, search)) ||
40+
contains(Path.get(val, key), search)) {
41+
res.push(item)
42+
}
43+
}
3344
} else {
34-
return contains(item, search)
45+
if (contains(item, search)) {
46+
res.push(item)
47+
}
3548
}
36-
})
49+
}
50+
return res
3751
}
3852

3953
/**
@@ -44,6 +58,7 @@ exports.filterBy = function (arr, search, delimiter /* ...dataKeys */) {
4458
*/
4559

4660
exports.orderBy = function (arr, sortKey, reverse) {
61+
arr = toArray(arr)
4762
if (!sortKey) {
4863
return arr
4964
}

src/watcher.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var uid = 0
2121
* - {Boolean} sync
2222
* - {Boolean} lazy
2323
* - {Function} [preProcess]
24+
* - {Function} [postProcess]
2425
* @constructor
2526
*/
2627

@@ -110,6 +111,9 @@ Watcher.prototype.get = function () {
110111
if (this.filters) {
111112
value = scope._applyFilters(value, null, this.filters, false)
112113
}
114+
if (this.postProcess) {
115+
value = this.postProcess(value)
116+
}
113117
this.afterGet()
114118
return value
115119
}

test/unit/specs/directives/public/for/for_spec.js

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,28 @@ if (_.inBrowser) {
8888
expect(el.innerHTML).toBe('<div>aaa</div>')
8989
})
9090

91+
it('filter converting array to object', function () {
92+
new Vue({
93+
el: el,
94+
data: {
95+
items: [
96+
{ msg: 'aaa' },
97+
{ msg: 'bbb' }
98+
]
99+
},
100+
template: '<div v-for="item in items | test">{{item.msg}} {{$key}}</div>',
101+
filters: {
102+
test: function (val) {
103+
return {
104+
a: val[0],
105+
b: val[1]
106+
}
107+
}
108+
}
109+
})
110+
expect(el.innerHTML).toBe('<div>aaa a</div><div>bbb b</div>')
111+
})
112+
91113
it('component', function (done) {
92114
var vm = new Vue({
93115
el: el,
@@ -660,22 +682,6 @@ if (_.inBrowser) {
660682
expect(hasWarned(_, 'It seems you are using two-way binding')).toBe(true)
661683
})
662684

663-
it('warn filters that return non-Array values', function () {
664-
new Vue({
665-
el: el,
666-
template: '<div v-for="item in items | test"></div>',
667-
data: {
668-
items: []
669-
},
670-
filters: {
671-
test: function (val) {
672-
return {}
673-
}
674-
}
675-
})
676-
expect(hasWarned(_, 'should always return Arrays')).toBe(true)
677-
})
678-
679685
it('nested track by', function (done) {
680686
var vm = new Vue({
681687
el: el,

0 commit comments

Comments
 (0)
0