Skip to content

Commit 155a8cc

Browse files
committed
refactor prop handling (fix vuejs#2587)
1 parent de0ac50 commit 155a8cc

File tree

3 files changed

+114
-32
lines changed

3 files changed

+114
-32
lines changed

src/compiler/compile-props.js

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import config from '../config'
22
import { parseDirective } from '../parsers/directive'
3-
import { defineReactive } from '../observer/index'
3+
import { isSimplePath } from '../parsers/expression'
4+
import { defineReactive, withoutConversion } from '../observer/index'
45
import propDef from '../directives/internal/prop'
56
import {
67
warn,
@@ -216,23 +217,61 @@ function makePropsLinkFn (props) {
216217
}
217218

218219
/**
219-
* Set a prop's initial value on a vm and its data object.
220+
* Process a prop with a rawValue, applying necessary coersions,
221+
* default values & assertions and call the given callback with
222+
* processed value.
220223
*
221224
* @param {Vue} vm
222225
* @param {Object} prop
223-
* @param {*} value
226+
* @param {*} rawValue
227+
* @param {Function} fn
224228
*/
225229

226-
export function initProp (vm, prop, value) {
227-
const key = prop.path
228-
value = coerceProp(prop, value)
230+
function processPropValue (vm, prop, rawValue, fn) {
231+
const isSimple = prop.dynamic && isSimplePath(prop.parentPath)
232+
let value = coerceProp(prop, rawValue)
229233
if (value === undefined) {
230234
value = getPropDefaultValue(vm, prop)
231235
}
236+
const coerced = value !== rawValue
232237
if (!assertProp(prop, value, vm)) {
233238
value = undefined
234239
}
235-
defineReactive(vm, key, value)
240+
if (isSimple && !coerced) {
241+
withoutConversion(() => {
242+
fn(value)
243+
})
244+
} else {
245+
fn(value)
246+
}
247+
}
248+
249+
/**
250+
* Set a prop's initial value on a vm and its data object.
251+
*
252+
* @param {Vue} vm
253+
* @param {Object} prop
254+
* @param {*} value
255+
*/
256+
257+
export function initProp (vm, prop, value) {
258+
processPropValue(vm, prop, value, value => {
259+
defineReactive(vm, prop.path, value)
260+
})
261+
}
262+
263+
/**
264+
* Update a prop's value on a vm.
265+
*
266+
* @param {Vue} vm
267+
* @param {Object} prop
268+
* @param {*} value
269+
*/
270+
271+
export function updateProp (vm, prop, value) {
272+
processPropValue(vm, prop, value, value => {
273+
vm[prop.path] = value
274+
})
236275
}
237276

238277
/**
@@ -276,7 +315,7 @@ function getPropDefaultValue (vm, prop) {
276315
* @param {Vue} vm
277316
*/
278317

279-
export function assertProp (prop, value, vm) {
318+
function assertProp (prop, value, vm) {
280319
if (
281320
!prop.options.required && ( // non-required
282321
prop.raw === null || // abscent
@@ -331,7 +370,7 @@ export function assertProp (prop, value, vm) {
331370
* @return {*}
332371
*/
333372

334-
export function coerceProp (prop, value) {
373+
function coerceProp (prop, value) {
335374
var coerce = prop.options.coerce
336375
if (!coerce) {
337376
return value
@@ -340,6 +379,14 @@ export function coerceProp (prop, value) {
340379
return coerce(value)
341380
}
342381

382+
/**
383+
* Assert the type of a value
384+
*
385+
* @param {*} value
386+
* @param {Function} type
387+
* @return {Object}
388+
*/
389+
343390
function assertType (value, type) {
344391
var valid
345392
var expectedType
@@ -370,12 +417,26 @@ function assertType (value, type) {
370417
}
371418
}
372419

420+
/**
421+
* Format type for output
422+
*
423+
* @param {String} type
424+
* @return {String}
425+
*/
426+
373427
function formatType (type) {
374428
return type
375429
? type.charAt(0).toUpperCase() + type.slice(1)
376430
: 'custom type'
377431
}
378432

433+
/**
434+
* Format value
435+
*
436+
* @param {*} value
437+
* @return {String}
438+
*/
439+
379440
function formatValue (val) {
380441
return Object.prototype.toString.call(val).slice(8, -1)
381442
}

src/directives/internal/prop.js

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55

66
import Watcher from '../../watcher'
77
import config from '../../config'
8-
import { isSimplePath } from '../../parsers/expression'
9-
import { withoutConversion } from '../../observer/index'
10-
import { assertProp, initProp, coerceProp } from '../../compiler/compile-props'
8+
import { initProp, updateProp } from '../../compiler/compile-props'
119

1210
const bindingModes = config._propBindingModes
1311

@@ -21,22 +19,12 @@ export default {
2119
const childKey = prop.path
2220
const parentKey = prop.parentPath
2321
const twoWay = prop.mode === bindingModes.TWO_WAY
24-
const isSimple = isSimplePath(parentKey)
2522

2623
const parentWatcher = this.parentWatcher = new Watcher(
2724
parent,
2825
parentKey,
2926
function (val) {
30-
val = coerceProp(prop, val)
31-
if (assertProp(prop, val, child)) {
32-
if (isSimple) {
33-
withoutConversion(() => {
34-
child[childKey] = val
35-
})
36-
} else {
37-
child[childKey] = val
38-
}
39-
}
27+
updateProp(child, prop, val)
4028
}, {
4129
twoWay: twoWay,
4230
filters: prop.filters,
@@ -47,14 +35,7 @@ export default {
4735
)
4836

4937
// set the child initial value.
50-
const value = parentWatcher.value
51-
if (isSimple && value !== undefined) {
52-
withoutConversion(() => {
53-
initProp(child, prop, value)
54-
})
55-
} else {
56-
initProp(child, prop, value)
57-
}
38+
initProp(child, prop, parentWatcher.value)
5839

5940
// setup two-way binding
6041
if (twoWay) {

test/unit/specs/directives/internal/prop_spec.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,10 +691,13 @@ describe('prop', function () {
691691
expect('did you mean `prop-c`').toHaveBeenWarned()
692692
})
693693

694-
it('should use default for undefined values', function () {
694+
it('should use default for undefined values', function (done) {
695695
var vm = new Vue({
696696
el: el,
697697
template: '<comp :a="a"></comp>',
698+
data: {
699+
a: undefined
700+
},
698701
components: {
699702
comp: {
700703
template: '{{a}}',
@@ -707,6 +710,15 @@ describe('prop', function () {
707710
}
708711
})
709712
expect(vm.$el.textContent).toBe('1')
713+
vm.a = 2
714+
Vue.nextTick(function () {
715+
expect(vm.$el.textContent).toBe('2')
716+
vm.a = undefined
717+
Vue.nextTick(function () {
718+
expect(vm.$el.textContent).toBe('1')
719+
done()
720+
})
721+
})
710722
})
711723

712724
it('non reactive values passed down as prop should not be converted', function (done) {
@@ -814,4 +826,32 @@ describe('prop', function () {
814826
done()
815827
})
816828
})
829+
830+
it('prop coerced value should be reactive', function (done) {
831+
var vm = new Vue({
832+
el: el,
833+
template: '<comp :obj="obj"></comp>',
834+
data: {
835+
obj: { ok: true }
836+
},
837+
components: {
838+
comp: {
839+
props: {
840+
obj: {
841+
coerce: function () {
842+
return { ok: false }
843+
}
844+
}
845+
},
846+
template: '<div>{{ obj.ok }}</div>'
847+
}
848+
}
849+
})
850+
expect(vm.$el.textContent).toBe('false')
851+
vm.$children[0].obj.ok = true
852+
Vue.nextTick(function () {
853+
expect(vm.$el.textContent).toBe('true')
854+
done()
855+
})
856+
})
817857
})

0 commit comments

Comments
 (0)