Skip to content

Commit 0c78d63

Browse files
committed
make props available in created() and data()
1 parent 8ad3e94 commit 0c78d63

File tree

13 files changed

+144
-124
lines changed

13 files changed

+144
-124
lines changed

examples/grid/grid.js

+6-9
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,18 @@ Vue.component('demo-grid', {
44
replace: true,
55
props: ['data', 'columns', 'filter-key'],
66
data: function () {
7+
var reversed = {}
8+
this.columns.forEach(function (key) {
9+
reversed[key] = false
10+
})
711
return {
812
data: null,
913
columns: null,
1014
sortKey: '',
1115
filterKey: '',
12-
reversed: {}
16+
reversed: reversed
1317
}
1418
},
15-
compiled: function () {
16-
// initialize reverse state
17-
var self = this
18-
this.columns.forEach(function (key) {
19-
self.reversed.$add(key, false)
20-
})
21-
},
2219
methods: {
2320
sortBy: function (key) {
2421
this.sortKey = key
@@ -40,4 +37,4 @@ var demo = new Vue({
4037
{ name: 'Jet Li', power: 8000 }
4138
]
4239
}
43-
})
40+
})

src/compiler/compile.js

+31-27
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,30 @@ function teardownDirs (vm, dirs, destroying) {
127127
}
128128

129129
/**
130-
* Compile the root element of an instance. There are
131-
* 3 types of things to process here:
130+
* Compile link props on an instance.
132131
*
133-
* 1. props on parent container (child scope)
134-
* 2. other attrs on parent container (parent scope)
135-
* 3. attrs on the component template root node, if
132+
* @param {Vue} vm
133+
* @param {Element} el
134+
* @param {Object} options
135+
* @return {Function}
136+
*/
137+
138+
exports.compileAndLinkProps = function (vm, el, props) {
139+
var propsLinkFn = compileProps(el, props)
140+
var propDirs = linkAndCapture(function () {
141+
propsLinkFn(vm, null)
142+
}, vm)
143+
return makeUnlinkFn(vm, propDirs)
144+
}
145+
146+
/**
147+
* Compile the root element of an instance.
148+
*
149+
* 1. attrs on parent container (parent scope)
150+
* 2. attrs on the component template root node, if
136151
* replace:true (child scope)
137152
*
138-
* Also, if this is a block instance, we only need to
139-
* compile 1 & 2 here.
153+
* If this is a block instance, we only need to compile 1.
140154
*
141155
* This function does compile and link at the same time,
142156
* since root linkers can not be reused. It returns the
@@ -152,13 +166,7 @@ function teardownDirs (vm, dirs, destroying) {
152166
exports.compileAndLinkRoot = function (vm, el, options) {
153167
var containerAttrs = options._containerAttrs
154168
var replacerAttrs = options._replacerAttrs
155-
var props = options.props
156-
var propsLinkFn, parentLinkFn, replacerLinkFn
157-
158-
// 1. props
159-
propsLinkFn = props
160-
? compileProps(el, containerAttrs || {}, props)
161-
: null
169+
var parentLinkFn, replacerLinkFn
162170

163171
// only need to compile other attributes for
164172
// non-block instances
@@ -191,7 +199,6 @@ function teardownDirs (vm, dirs, destroying) {
191199

192200
// link self
193201
var selfDirs = linkAndCapture(function () {
194-
if (propsLinkFn) propsLinkFn(vm, null)
195202
if (replacerLinkFn) replacerLinkFn(vm, el)
196203
}, vm)
197204

@@ -406,7 +413,6 @@ function makeChildLinkFn (linkFns) {
406413
* a props link function.
407414
*
408415
* @param {Element|DocumentFragment} el
409-
* @param {Object} attrs
410416
* @param {Array} propDescriptors
411417
* @return {Function} propsLinkFn
412418
*/
@@ -416,7 +422,7 @@ var settablePathRE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*|\[[^\[\]]+\])*$/
416422
var literalValueRE = /^(true|false)$|^\d.*/
417423
var identRE = require('../parsers/path').identRE
418424

419-
function compileProps (el, attrs, propDescriptors) {
425+
function compileProps (el, propDescriptors) {
420426
var props = []
421427
var i = propDescriptors.length
422428
var descriptor, name, assertions, value, path, prop, literal, single
@@ -449,9 +455,12 @@ function compileProps (el, attrs, propDescriptors) {
449455
'must be valid identifiers.'
450456
)
451457
}
452-
value = attrs[name]
453-
/* jshint eqeqeq:false */
454-
if (value != null) {
458+
value = el.getAttribute(name)
459+
if (value !== null) {
460+
// important so that this doesn't get compiled
461+
// again as a normal attribute binding
462+
el.removeAttribute(name)
463+
// create a prop descriptor
455464
prop = {
456465
name: name,
457466
raw: value,
@@ -464,9 +473,6 @@ function compileProps (el, attrs, propDescriptors) {
464473
if (el && el.nodeType === 1) {
465474
el.removeAttribute(name)
466475
}
467-
// important so that this doesn't get compiled
468-
// again as a normal attribute binding
469-
attrs[name] = null
470476
prop.dynamic = true
471477
prop.parentPath = textParser.tokensToExp(tokens)
472478
// check prop binding type.
@@ -491,9 +497,7 @@ function compileProps (el, attrs, propDescriptors) {
491497
}
492498
props.push(prop)
493499
} else if (assertions && assertions.required) {
494-
_.warn(
495-
'Missing required prop: ' + name
496-
)
500+
_.warn('Missing required prop: ' + name)
497501
}
498502
}
499503
return makePropsLinkFn(props)
@@ -514,6 +518,7 @@ function makePropsLinkFn (props) {
514518
prop = props[i]
515519
path = prop.path
516520
if (prop.dynamic) {
521+
// dynamic prop
517522
if (vm.$parent) {
518523
if (prop.mode === propBindingModes.ONE_TIME) {
519524
// one time binding
@@ -654,7 +659,6 @@ function compileDirectives (elOrAttrs, options) {
654659
attr = attrs[i]
655660
name = attr.name
656661
value = attr.value
657-
if (value === null) continue
658662
if (name.indexOf(config.prefix) === 0) {
659663
dirName = name.slice(config.prefix.length)
660664
dirDef = resolveAsset(options, 'directives', dirName)

src/directives/prop.js

+26-12
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
// NOTE: the prop internal directive is compiled and linked
2+
// during _initScope(), before the created hook is called.
3+
// The purpose is to make the initial prop values available
4+
// inside `created` hooks and `data` functions.
5+
16
var _ = require('../util')
27
var Watcher = require('../watcher')
38
var bindingModes = require('../config')._propBindingModes
@@ -38,25 +43,34 @@ module.exports = {
3843
}
3944
})
4045
)
41-
42-
// set the child initial value first, before setting
43-
// up the child watcher to avoid triggering it
44-
// immediately.
46+
47+
// set the child initial value.
48+
// !!! We need to set it also on raw data here, because
49+
// props are initialized before data is fully observed
4550
var value = this.parentWatcher.value
4651
if (_.assertProp(prop, value)) {
47-
child.$set(childKey, value)
52+
if (childKey === '$data') {
53+
child._data = value
54+
} else {
55+
child[childKey] = child._data[childKey] = value
56+
}
4857
}
4958

5059
// only setup two-way binding if this is not a one-way
5160
// binding.
5261
if (prop.mode === bindingModes.TWO_WAY) {
53-
this.childWatcher = new Watcher(
54-
child,
55-
childKey,
56-
withLock(function (val) {
57-
parent.$set(parentKey, val)
58-
})
59-
)
62+
// important: defer the child watcher creation until
63+
// the created hook (after data observation)
64+
var self = this
65+
child.$once('hook:created', function () {
66+
self.childWatcher = new Watcher(
67+
child,
68+
childKey,
69+
withLock(function (val) {
70+
parent[parentKey] = val
71+
})
72+
)
73+
})
6074
}
6175
},
6276

src/instance/compile.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ exports._destroy = function (remove, deferCleanup) {
139139
while (i--) {
140140
this._children[i].$destroy()
141141
}
142+
// teardown props
143+
if (this._propsUnlinkFn) {
144+
this._propsUnlinkFn()
145+
}
142146
// teardown all directives. this also tearsdown all
143147
// directive-owned watchers.
144148
if (this._unlinkFn) {
@@ -185,4 +189,4 @@ exports._cleanup = function () {
185189
this._callHook('destroyed')
186190
// turn off all instance listeners.
187191
this.$off()
188-
}
192+
}

src/instance/init.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,9 @@ exports._init = function (options) {
7373
this
7474
)
7575

76-
// set data after merge.
77-
this._data = options.data || {}
76+
// initialize data as empty object.
77+
// it will be filled up in _initScope().
78+
this._data = {}
7879

7980
// initialize data observation and scope inheritance.
8081
this._initScope()

src/instance/misc.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,4 @@ exports._resolveComponent = function (id, cb) {
8383
// normal component
8484
cb(factory)
8585
}
86-
}
86+
}

src/instance/scope.js

+22-20
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
var _ = require('../util')
2+
var compiler = require('../compiler')
23
var Observer = require('../observer')
34
var Dep = require('../observer/dep')
45

@@ -12,35 +13,30 @@ var Dep = require('../observer/dep')
1213

1314
exports._initScope = function () {
1415
this._initProps()
16+
this._initMeta()
17+
this._initMethods()
1518
this._initData()
1619
this._initComputed()
17-
this._initMethods()
18-
this._initMeta()
1920
}
2021

2122
/**
2223
* Initialize props.
2324
*/
2425

2526
exports._initProps = function () {
26-
// make sure all props properties are observed
27-
var data = this._data
28-
var props = this.$options.props
29-
var prop, key, i
30-
if (props) {
31-
i = props.length
32-
while (i--) {
33-
prop = props[i]
34-
// props can be strings or object descriptors
35-
key = _.camelize(
36-
(typeof prop === 'string'
37-
? prop
38-
: prop.name).replace(/^data-/, '')
27+
var options = this.$options
28+
var el = options.el
29+
var props = options.props
30+
this._propsUnlinkFn = el && props
31+
? compiler.compileAndLinkProps(
32+
this, el, props
3933
)
40-
if (!(key in data) && key !== '$data') {
41-
data[key] = undefined
42-
}
43-
}
34+
: null
35+
if (props && !el) {
36+
_.warn(
37+
'Props will not be compiled if no `el` option is ' +
38+
'provided at instantiation.'
39+
)
4440
}
4541
}
4642

@@ -49,8 +45,14 @@ exports._initProps = function () {
4945
*/
5046

5147
exports._initData = function () {
52-
// proxy data on instance
48+
var propsData = this._data
49+
var optionsDataFn = this.$options.data
50+
var optionsData = optionsDataFn && optionsDataFn()
51+
if (optionsData) {
52+
this._data = _.extend(optionsData, propsData)
53+
}
5354
var data = this._data
55+
// proxy data on instance
5456
var keys = Object.keys(data)
5557
var i, key
5658
i = keys.length

src/util/options.js

+14-12
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,20 @@ strats.data = function (parentVal, childVal, vm) {
6565
parentVal.call(this)
6666
)
6767
}
68-
} else {
69-
// instance merge, return raw object
70-
var instanceData = typeof childVal === 'function'
71-
? childVal.call(vm)
72-
: childVal
73-
var defaultData = typeof parentVal === 'function'
74-
? parentVal.call(vm)
75-
: undefined
76-
if (instanceData) {
77-
return mergeData(instanceData, defaultData)
78-
} else {
79-
return defaultData
68+
} else if (parentVal || childVal) {
69+
return function mergedInstanceDataFn () {
70+
// instance merge
71+
var instanceData = typeof childVal === 'function'
72+
? childVal.call(vm)
73+
: childVal
74+
var defaultData = typeof parentVal === 'function'
75+
? parentVal.call(vm)
76+
: undefined
77+
if (instanceData) {
78+
return mergeData(instanceData, defaultData)
79+
} else {
80+
return defaultData
81+
}
8082
}
8183
}
8284
}

src/watcher.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ p.get = function () {
8686
if (config.warnExpressionErrors) {
8787
_.warn(
8888
'Error when evaluating expression "' +
89-
this.expression + '"', e
89+
this.expression + '". ' +
90+
(config.debug
91+
? '' :
92+
'Turn on debug mode to see stack trace.'
93+
), e
9094
)
9195
}
9296
}

0 commit comments

Comments
 (0)