1 |
|
2 |
|
3 | import config from '../config'
|
4 | import Watcher from '../observer/watcher'
|
5 | import Dep, { pushTarget, popTarget } from '../observer/dep'
|
6 | import { isUpdatingChildComponent } from './lifecycle'
|
7 |
|
8 | import {
|
9 | set,
|
10 | del,
|
11 | observe,
|
12 | defineReactive,
|
13 | toggleObserving
|
14 | } from '../observer/index'
|
15 |
|
16 | import {
|
17 | warn,
|
18 | bind,
|
19 | noop,
|
20 | hasOwn,
|
21 | hyphenate,
|
22 | isReserved,
|
23 | handleError,
|
24 | nativeWatch,
|
25 | validateProp,
|
26 | isPlainObject,
|
27 | isServerRendering,
|
28 | isReservedAttribute
|
29 | } from '../util/index'
|
30 |
|
31 | const sharedPropertyDefinition = {
|
32 | enumerable: true,
|
33 | configurable: true,
|
34 | get: noop,
|
35 | set: noop
|
36 | }
|
37 |
|
38 | export function proxy (target: Object, sourceKey: string, key: string) {
|
39 | sharedPropertyDefinition.get = function proxyGetter () {
|
40 | return this[sourceKey][key]
|
41 | }
|
42 | sharedPropertyDefinition.set = function proxySetter (val) {
|
43 | this[sourceKey][key] = val
|
44 | }
|
45 | Object.defineProperty(target, key, sharedPropertyDefinition)
|
46 | }
|
47 |
|
48 | export function initState (vm: Component) {
|
49 | vm._watchers = []
|
50 | const opts = vm.$options
|
51 | if (opts.props) initProps(vm, opts.props)
|
52 | if (opts.methods) initMethods(vm, opts.methods)
|
53 | if (opts.data) {
|
54 | initData(vm)
|
55 | } else {
|
56 | observe(vm._data = {}, true )
|
57 | }
|
58 | if (opts.computed) initComputed(vm, opts.computed)
|
59 | if (opts.watch && opts.watch !== nativeWatch) {
|
60 | initWatch(vm, opts.watch)
|
61 | }
|
62 | }
|
63 |
|
64 | function initProps (vm: Component, propsOptions: Object) {
|
65 | const propsData = vm.$options.propsData || {}
|
66 | const props = vm._props = {}
|
67 |
|
68 |
|
69 | const keys = vm.$options._propKeys = []
|
70 | const isRoot = !vm.$parent
|
71 |
|
72 | if (!isRoot) {
|
73 | toggleObserving(false)
|
74 | }
|
75 | for (const key in propsOptions) {
|
76 | keys.push(key)
|
77 | const value = validateProp(key, propsOptions, propsData, vm)
|
78 |
|
79 | if (process.env.NODE_ENV !== 'production') {
|
80 | const hyphenatedKey = hyphenate(key)
|
81 | if (isReservedAttribute(hyphenatedKey) ||
|
82 | config.isReservedAttr(hyphenatedKey)) {
|
83 | warn(
|
84 | `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
|
85 | vm
|
86 | )
|
87 | }
|
88 | defineReactive(props, key, value, () => {
|
89 | if (!isRoot && !isUpdatingChildComponent) {
|
90 | warn(
|
91 | `Avoid mutating a prop directly since the value will be ` +
|
92 | `overwritten whenever the parent component re-renders. ` +
|
93 | `Instead, use a data or computed property based on the prop's ` +
|
94 | `value. Prop being mutated: "${key}"`,
|
95 | vm
|
96 | )
|
97 | }
|
98 | })
|
99 | } else {
|
100 | defineReactive(props, key, value)
|
101 | }
|
102 |
|
103 |
|
104 |
|
105 | if (!(key in vm)) {
|
106 | proxy(vm, `_props`, key)
|
107 | }
|
108 | }
|
109 | toggleObserving(true)
|
110 | }
|
111 |
|
112 | function initData (vm: Component) {
|
113 | let data = vm.$options.data
|
114 | data = vm._data = typeof data === 'function'
|
115 | ? getData(data, vm)
|
116 | : data || {}
|
117 | if (!isPlainObject(data)) {
|
118 | data = {}
|
119 | process.env.NODE_ENV !== 'production' && warn(
|
120 | 'data functions should return an object:\n' +
|
121 | 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
|
122 | vm
|
123 | )
|
124 | }
|
125 |
|
126 | const keys = Object.keys(data)
|
127 | const props = vm.$options.props
|
128 | const methods = vm.$options.methods
|
129 | let i = keys.length
|
130 | while (i--) {
|
131 | const key = keys[i]
|
132 | if (process.env.NODE_ENV !== 'production') {
|
133 | if (methods && hasOwn(methods, key)) {
|
134 | warn(
|
135 | `Method "${key}" has already been defined as a data property.`,
|
136 | vm
|
137 | )
|
138 | }
|
139 | }
|
140 | if (props && hasOwn(props, key)) {
|
141 | process.env.NODE_ENV !== 'production' && warn(
|
142 | `The data property "${key}" is already declared as a prop. ` +
|
143 | `Use prop default value instead.`,
|
144 | vm
|
145 | )
|
146 | } else if (!isReserved(key)) {
|
147 | proxy(vm, `_data`, key)
|
148 | }
|
149 | }
|
150 |
|
151 | observe(data, true )
|
152 | }
|
153 |
|
154 | export function getData (data: Function, vm: Component): any {
|
155 |
|
156 | pushTarget()
|
157 | try {
|
158 | return data.call(vm, vm)
|
159 | } catch (e) {
|
160 | handleError(e, vm, `data()`)
|
161 | return {}
|
162 | } finally {
|
163 | popTarget()
|
164 | }
|
165 | }
|
166 |
|
167 | const computedWatcherOptions = { lazy: true }
|
168 |
|
169 | function initComputed (vm: Component, computed: Object) {
|
170 |
|
171 | const watchers = vm._computedWatchers = Object.create(null)
|
172 |
|
173 | const isSSR = isServerRendering()
|
174 |
|
175 | for (const key in computed) {
|
176 | const userDef = computed[key]
|
177 | const getter = typeof userDef === 'function' ? userDef : userDef.get
|
178 | if (process.env.NODE_ENV !== 'production' && getter == null) {
|
179 | warn(
|
180 | `Getter is missing for computed property "${key}".`,
|
181 | vm
|
182 | )
|
183 | }
|
184 |
|
185 | if (!isSSR) {
|
186 |
|
187 | watchers[key] = new Watcher(
|
188 | vm,
|
189 | getter || noop,
|
190 | noop,
|
191 | computedWatcherOptions
|
192 | )
|
193 | }
|
194 |
|
195 |
|
196 |
|
197 |
|
198 | if (!(key in vm)) {
|
199 | defineComputed(vm, key, userDef)
|
200 | } else if (process.env.NODE_ENV !== 'production') {
|
201 | if (key in vm.$data) {
|
202 | warn(`The computed property "${key}" is already defined in data.`, vm)
|
203 | } else if (vm.$options.props && key in vm.$options.props) {
|
204 | warn(`The computed property "${key}" is already defined as a prop.`, vm)
|
205 | }
|
206 | }
|
207 | }
|
208 | }
|
209 |
|
210 | export function defineComputed (
|
211 | target: any,
|
212 | key: string,
|
213 | userDef: Object | Function
|
214 | ) {
|
215 | const shouldCache = !isServerRendering()
|
216 | if (typeof userDef === 'function') {
|
217 | sharedPropertyDefinition.get = shouldCache
|
218 | ? createComputedGetter(key)
|
219 | : createGetterInvoker(userDef)
|
220 | sharedPropertyDefinition.set = noop
|
221 | } else {
|
222 | sharedPropertyDefinition.get = userDef.get
|
223 | ? shouldCache && userDef.cache !== false
|
224 | ? createComputedGetter(key)
|
225 | : createGetterInvoker(userDef.get)
|
226 | : noop
|
227 | sharedPropertyDefinition.set = userDef.set || noop
|
228 | }
|
229 | if (process.env.NODE_ENV !== 'production' &&
|
230 | sharedPropertyDefinition.set === noop) {
|
231 | sharedPropertyDefinition.set = function () {
|
232 | warn(
|
233 | `Computed property "${key}" was assigned to but it has no setter.`,
|
234 | this
|
235 | )
|
236 | }
|
237 | }
|
238 | Object.defineProperty(target, key, sharedPropertyDefinition)
|
239 | }
|
240 |
|
241 | function createComputedGetter (key) {
|
242 | return function computedGetter () {
|
243 | const watcher = this._computedWatchers && this._computedWatchers[key]
|
244 | if (watcher) {
|
245 | if (watcher.dirty) {
|
246 | watcher.evaluate()
|
247 | }
|
248 | if (Dep.target) {
|
249 | watcher.depend()
|
250 | }
|
251 | return watcher.value
|
252 | }
|
253 | }
|
254 | }
|
255 |
|
256 | function createGetterInvoker(fn) {
|
257 | return function computedGetter () {
|
258 | return fn.call(this, this)
|
259 | }
|
260 | }
|
261 |
|
262 | function initMethods (vm: Component, methods: Object) {
|
263 | const props = vm.$options.props
|
264 | for (const key in methods) {
|
265 | if (process.env.NODE_ENV !== 'production') {
|
266 | if (typeof methods[key] !== 'function') {
|
267 | warn(
|
268 | `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
|
269 | `Did you reference the function correctly?`,
|
270 | vm
|
271 | )
|
272 | }
|
273 | if (props && hasOwn(props, key)) {
|
274 | warn(
|
275 | `Method "${key}" has already been defined as a prop.`,
|
276 | vm
|
277 | )
|
278 | }
|
279 | if ((key in vm) && isReserved(key)) {
|
280 | warn(
|
281 | `Method "${key}" conflicts with an existing Vue instance method. ` +
|
282 | `Avoid defining component methods that start with _ or $.`
|
283 | )
|
284 | }
|
285 | }
|
286 | vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
|
287 | }
|
288 | }
|
289 |
|
290 | function initWatch (vm: Component, watch: Object) {
|
291 | for (const key in watch) {
|
292 | const handler = watch[key]
|
293 | if (Array.isArray(handler)) {
|
294 | for (let i = 0; i < handler.length; i++) {
|
295 | createWatcher(vm, key, handler[i])
|
296 | }
|
297 | } else {
|
298 | createWatcher(vm, key, handler)
|
299 | }
|
300 | }
|
301 | }
|
302 |
|
303 | function createWatcher (
|
304 | vm: Component,
|
305 | expOrFn: string | Function,
|
306 | handler: any,
|
307 | options?: Object
|
308 | ) {
|
309 | if (isPlainObject(handler)) {
|
310 | options = handler
|
311 | handler = handler.handler
|
312 | }
|
313 | if (typeof handler === 'string') {
|
314 | handler = vm[handler]
|
315 | }
|
316 | return vm.$watch(expOrFn, handler, options)
|
317 | }
|
318 |
|
319 | export function stateMixin (Vue: Class<Component>) {
|
320 |
|
321 |
|
322 |
|
323 | const dataDef = {}
|
324 | dataDef.get = function () { return this._data }
|
325 | const propsDef = {}
|
326 | propsDef.get = function () { return this._props }
|
327 | if (process.env.NODE_ENV !== 'production') {
|
328 | dataDef.set = function () {
|
329 | warn(
|
330 | 'Avoid replacing instance root $data. ' +
|
331 | 'Use nested data properties instead.',
|
332 | this
|
333 | )
|
334 | }
|
335 | propsDef.set = function () {
|
336 | warn(`$props is readonly.`, this)
|
337 | }
|
338 | }
|
339 | Object.defineProperty(Vue.prototype, '$data', dataDef)
|
340 | Object.defineProperty(Vue.prototype, '$props', propsDef)
|
341 |
|
342 | Vue.prototype.$set = set
|
343 | Vue.prototype.$delete = del
|
344 |
|
345 | Vue.prototype.$watch = function (
|
346 | expOrFn: string | Function,
|
347 | cb: any,
|
348 | options?: Object
|
349 | ): Function {
|
350 | const vm: Component = this
|
351 | if (isPlainObject(cb)) {
|
352 | return createWatcher(vm, expOrFn, cb, options)
|
353 | }
|
354 | options = options || {}
|
355 | options.user = true
|
356 | const watcher = new Watcher(vm, expOrFn, cb, options)
|
357 | if (options.immediate) {
|
358 | try {
|
359 | cb.call(vm, watcher.value)
|
360 | } catch (error) {
|
361 | handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
|
362 | }
|
363 | }
|
364 | return function unwatchFn () {
|
365 | watcher.teardown()
|
366 | }
|
367 | }
|
368 | }
|