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

Skip to content

Commit a6e3c0f

Browse files
committed
v-for: no longer pre-convert values into arrays before piping it through filters
1 parent 559768d commit a6e3c0f

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
@@ -113,11 +113,12 @@ Directive.prototype._bind = function () {
113113
} else {
114114
this._update = noop
115115
}
116-
// pre-process hook called before the value is piped
117-
// through the filters. used in v-repeat.
118116
var preProcess = this._preProcess
119117
? _.bind(this._preProcess, this)
120118
: null
119+
var postProcess = this._postProcess
120+
? _.bind(this._postProcess, this)
121+
: null
121122
var watcher = this._watcher = new Watcher(
122123
this.vm,
123124
this._watcherExp,
@@ -127,6 +128,7 @@ Directive.prototype._bind = function () {
127128
twoWay: this.twoWay,
128129
deep: this.deep,
129130
preProcess: preProcess,
131+
postProcess: postProcess,
130132
scope: this._scope
131133
}
132134
)

src/directives/for.js

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

7272
update: function (data) {
73-
if (process.env.NODE_ENV !== 'production' && !_.isArray(data)) {
74-
_.warn(
75-
'v-for pre-converts Objects into Arrays, and ' +
76-
'v-for filters should always return Arrays.'
77-
)
78-
}
7973
this.diff(data)
8074
this.updateRef()
8175
this.updateModel()
@@ -96,25 +90,31 @@ module.exports = {
9690
*/
9791

9892
diff: function (data) {
93+
// check if the Array was converted from an Object
94+
var item = data[0]
95+
var convertedFromObject = this.fromObject =
96+
isObject(item) &&
97+
item.hasOwnProperty('$key') &&
98+
item.hasOwnProperty('$value')
99+
99100
var idKey = this.idKey
100-
var converted = this.converted
101101
var oldFrags = this.frags
102102
var frags = this.frags = new Array(data.length)
103103
var alias = this.alias
104104
var start = this.start
105105
var end = this.end
106106
var inDoc = _.inDoc(start)
107107
var init = !oldFrags
108-
var i, l, frag, item, key, value, primitive
108+
var i, l, frag, key, value, primitive
109109

110110
// First pass, go through the new Array and fill up
111111
// the new frags array. If a piece of data has a cached
112112
// instance for it, we reuse it. Otherwise build a new
113113
// instance.
114114
for (i = 0, l = data.length; i < l; i++) {
115115
item = data[i]
116-
key = converted ? item.$key : null
117-
value = converted ? item.$value : item
116+
key = convertedFromObject ? item.$key : null
117+
value = convertedFromObject ? item.$value : item
118118
primitive = !isObject(value)
119119
frag = !init && this.getCachedFrag(value, i, key)
120120
if (frag) { // reusable fragment
@@ -135,7 +135,7 @@ module.exports = {
135135
}
136136
// update data for track-by, object repeat &
137137
// primitive values.
138-
if (idKey || converted || primitive) {
138+
if (idKey || convertedFromObject || primitive) {
139139
frag.scope[alias] = value
140140
}
141141
} else { // new isntance
@@ -238,7 +238,7 @@ module.exports = {
238238
if (!ref) return
239239
var hash = (this._scope || this.vm).$
240240
var refs
241-
if (!this.converted) {
241+
if (!this.fromObject) {
242242
refs = this.frags.map(findVmFromFrag)
243243
} else {
244244
refs = {}
@@ -466,29 +466,28 @@ module.exports = {
466466

467467
/**
468468
* Pre-process the value before piping it through the
469-
* filters, and convert non-Array objects to arrays.
470-
*
471-
* This function will be bound to this directive instance
472-
* and passed into the watcher.
473-
*
474-
* @param {*} value
475-
* @return {Array}
476-
* @private
469+
* filters. This is passed to and called by the watcher.
477470
*/
478471

479472
_preProcess: function (value) {
480473
// regardless of type, store the un-filtered raw value.
481474
this.rawValue = value
482-
var type = this.rawType = typeof value
483-
if (!_.isPlainObject(value)) {
484-
this.converted = false
485-
if (type === 'number') {
486-
value = range(value)
487-
} else if (type === 'string') {
488-
value = _.toArray(value)
489-
}
490-
return value || []
491-
} else {
475+
return value
476+
},
477+
478+
/**
479+
* Post-process the value after it has been piped through
480+
* the filters. This is passed to and called by the watcher.
481+
*
482+
* It is necessary for this to be called during the
483+
* wathcer's dependency collection phase because we want
484+
* the v-for to update when the source Object is mutated.
485+
*/
486+
487+
_postProcess: function (value) {
488+
if (_.isArray(value)) {
489+
return value
490+
} else if (_.isPlainObject(value)) {
492491
// convert plain object to array.
493492
var keys = Object.keys(value)
494493
var i = keys.length
@@ -501,8 +500,15 @@ module.exports = {
501500
$value: value[key]
502501
}
503502
}
504-
this.converted = true
505503
return res
504+
} else {
505+
var type = typeof value
506+
if (type === 'number') {
507+
value = range(value)
508+
} else if (type === 'string') {
509+
value = _.toArray(value)
510+
}
511+
return value || []
506512
}
507513
},
508514

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/for')._postProcess
34

45
/**
56
* Filter filter for v-repeat
@@ -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/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