import config from '../config'
|
import Watcher from '../observer/watcher'
|
import Dep, { pushTarget, popTarget } from '../observer/dep'
|
import { isUpdatingChildComponent } from './lifecycle'
|
import { initSetup } from 'v3/apiSetup'
|
|
import {
|
set,
|
del,
|
observe,
|
defineReactive,
|
toggleObserving
|
} from '../observer/index'
|
|
import {
|
warn,
|
bind,
|
noop,
|
hasOwn,
|
isArray,
|
hyphenate,
|
isReserved,
|
handleError,
|
nativeWatch,
|
validateProp,
|
isPlainObject,
|
isServerRendering,
|
isReservedAttribute,
|
invokeWithErrorHandling,
|
isFunction
|
} from '../util/index'
|
import type { Component } from 'types/component'
|
import { shallowReactive, TrackOpTypes } from 'v3'
|
|
const sharedPropertyDefinition = {
|
enumerable: true,
|
configurable: true,
|
get: noop,
|
set: noop
|
}
|
|
export function proxy(target: Object, sourceKey: string, key: string) {
|
sharedPropertyDefinition.get = function proxyGetter() {
|
return this[sourceKey][key]
|
}
|
sharedPropertyDefinition.set = function proxySetter(val) {
|
this[sourceKey][key] = val
|
}
|
Object.defineProperty(target, key, sharedPropertyDefinition)
|
}
|
|
export function initState(vm: Component) {
|
const opts = vm.$options
|
if (opts.props) initProps(vm, opts.props)
|
|
// Composition API
|
initSetup(vm)
|
|
if (opts.methods) initMethods(vm, opts.methods)
|
if (opts.data) {
|
initData(vm)
|
} else {
|
const ob = observe((vm._data = {}))
|
ob && ob.vmCount++
|
}
|
if (opts.computed) initComputed(vm, opts.computed)
|
if (opts.watch && opts.watch !== nativeWatch) {
|
initWatch(vm, opts.watch)
|
}
|
}
|
|
function initProps(vm: Component, propsOptions: Object) {
|
const propsData = vm.$options.propsData || {}
|
const props = (vm._props = shallowReactive({}))
|
// cache prop keys so that future props updates can iterate using Array
|
// instead of dynamic object key enumeration.
|
const keys: string[] = (vm.$options._propKeys = [])
|
const isRoot = !vm.$parent
|
// root instance props should be converted
|
if (!isRoot) {
|
toggleObserving(false)
|
}
|
for (const key in propsOptions) {
|
keys.push(key)
|
const value = validateProp(key, propsOptions, propsData, vm)
|
/* istanbul ignore else */
|
if (__DEV__) {
|
const hyphenatedKey = hyphenate(key)
|
if (
|
isReservedAttribute(hyphenatedKey) ||
|
config.isReservedAttr(hyphenatedKey)
|
) {
|
warn(
|
`"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,
|
vm
|
)
|
}
|
defineReactive(
|
props,
|
key,
|
value,
|
() => {
|
if (!isRoot && !isUpdatingChildComponent) {
|
warn(
|
`Avoid mutating a prop directly since the value will be ` +
|
`overwritten whenever the parent component re-renders. ` +
|
`Instead, use a data or computed property based on the prop's ` +
|
`value. Prop being mutated: "${key}"`,
|
vm
|
)
|
}
|
},
|
true /* shallow */
|
)
|
} else {
|
defineReactive(props, key, value, undefined, true /* shallow */)
|
}
|
// static props are already proxied on the component's prototype
|
// during Vue.extend(). We only need to proxy props defined at
|
// instantiation here.
|
if (!(key in vm)) {
|
proxy(vm, `_props`, key)
|
}
|
}
|
toggleObserving(true)
|
}
|
|
function initData(vm: Component) {
|
let data: any = vm.$options.data
|
data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
|
if (!isPlainObject(data)) {
|
data = {}
|
__DEV__ &&
|
warn(
|
'data functions should return an object:\n' +
|
'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
|
vm
|
)
|
}
|
// proxy data on instance
|
const keys = Object.keys(data)
|
const props = vm.$options.props
|
const methods = vm.$options.methods
|
let i = keys.length
|
while (i--) {
|
const key = keys[i]
|
if (__DEV__) {
|
if (methods && hasOwn(methods, key)) {
|
warn(`Method "${key}" has already been defined as a data property.`, vm)
|
}
|
}
|
if (props && hasOwn(props, key)) {
|
__DEV__ &&
|
warn(
|
`The data property "${key}" is already declared as a prop. ` +
|
`Use prop default value instead.`,
|
vm
|
)
|
} else if (!isReserved(key)) {
|
proxy(vm, `_data`, key)
|
}
|
}
|
// observe data
|
const ob = observe(data)
|
ob && ob.vmCount++
|
}
|
|
export function getData(data: Function, vm: Component): any {
|
// #7573 disable dep collection when invoking data getters
|
pushTarget()
|
try {
|
return data.call(vm, vm)
|
} catch (e: any) {
|
handleError(e, vm, `data()`)
|
return {}
|
} finally {
|
popTarget()
|
}
|
}
|
|
const computedWatcherOptions = { lazy: true }
|
|
function initComputed(vm: Component, computed: Object) {
|
// $flow-disable-line
|
const watchers = (vm._computedWatchers = Object.create(null))
|
// computed properties are just getters during SSR
|
const isSSR = isServerRendering()
|
|
for (const key in computed) {
|
const userDef = computed[key]
|
const getter = isFunction(userDef) ? userDef : userDef.get
|
if (__DEV__ && getter == null) {
|
warn(`Getter is missing for computed property "${key}".`, vm)
|
}
|
|
if (!isSSR) {
|
// create internal watcher for the computed property.
|
watchers[key] = new Watcher(
|
vm,
|
getter || noop,
|
noop,
|
computedWatcherOptions
|
)
|
}
|
|
// component-defined computed properties are already defined on the
|
// component prototype. We only need to define computed properties defined
|
// at instantiation here.
|
if (!(key in vm)) {
|
defineComputed(vm, key, userDef)
|
} else if (__DEV__) {
|
if (key in vm.$data) {
|
warn(`The computed property "${key}" is already defined in data.`, vm)
|
} else if (vm.$options.props && key in vm.$options.props) {
|
warn(`The computed property "${key}" is already defined as a prop.`, vm)
|
} else if (vm.$options.methods && key in vm.$options.methods) {
|
warn(
|
`The computed property "${key}" is already defined as a method.`,
|
vm
|
)
|
}
|
}
|
}
|
}
|
|
export function defineComputed(
|
target: any,
|
key: string,
|
userDef: Record<string, any> | (() => any)
|
) {
|
const shouldCache = !isServerRendering()
|
if (isFunction(userDef)) {
|
sharedPropertyDefinition.get = shouldCache
|
? createComputedGetter(key)
|
: createGetterInvoker(userDef)
|
sharedPropertyDefinition.set = noop
|
} else {
|
sharedPropertyDefinition.get = userDef.get
|
? shouldCache && userDef.cache !== false
|
? createComputedGetter(key)
|
: createGetterInvoker(userDef.get)
|
: noop
|
sharedPropertyDefinition.set = userDef.set || noop
|
}
|
if (__DEV__ && sharedPropertyDefinition.set === noop) {
|
sharedPropertyDefinition.set = function () {
|
warn(
|
`Computed property "${key}" was assigned to but it has no setter.`,
|
this
|
)
|
}
|
}
|
Object.defineProperty(target, key, sharedPropertyDefinition)
|
}
|
|
function createComputedGetter(key) {
|
return function computedGetter() {
|
const watcher = this._computedWatchers && this._computedWatchers[key]
|
if (watcher) {
|
if (watcher.dirty) {
|
watcher.evaluate()
|
}
|
if (Dep.target) {
|
if (__DEV__ && Dep.target.onTrack) {
|
Dep.target.onTrack({
|
effect: Dep.target,
|
target: this,
|
type: TrackOpTypes.GET,
|
key
|
})
|
}
|
watcher.depend()
|
}
|
return watcher.value
|
}
|
}
|
}
|
|
function createGetterInvoker(fn) {
|
return function computedGetter() {
|
return fn.call(this, this)
|
}
|
}
|
|
function initMethods(vm: Component, methods: Object) {
|
const props = vm.$options.props
|
for (const key in methods) {
|
if (__DEV__) {
|
if (typeof methods[key] !== 'function') {
|
warn(
|
`Method "${key}" has type "${typeof methods[
|
key
|
]}" in the component definition. ` +
|
`Did you reference the function correctly?`,
|
vm
|
)
|
}
|
if (props && hasOwn(props, key)) {
|
warn(`Method "${key}" has already been defined as a prop.`, vm)
|
}
|
if (key in vm && isReserved(key)) {
|
warn(
|
`Method "${key}" conflicts with an existing Vue instance method. ` +
|
`Avoid defining component methods that start with _ or $.`
|
)
|
}
|
}
|
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
|
}
|
}
|
|
function initWatch(vm: Component, watch: Object) {
|
for (const key in watch) {
|
const handler = watch[key]
|
if (isArray(handler)) {
|
for (let i = 0; i < handler.length; i++) {
|
createWatcher(vm, key, handler[i])
|
}
|
} else {
|
createWatcher(vm, key, handler)
|
}
|
}
|
}
|
|
function createWatcher(
|
vm: Component,
|
expOrFn: string | (() => any),
|
handler: any,
|
options?: Object
|
) {
|
if (isPlainObject(handler)) {
|
options = handler
|
handler = handler.handler
|
}
|
if (typeof handler === 'string') {
|
handler = vm[handler]
|
}
|
return vm.$watch(expOrFn, handler, options)
|
}
|
|
export function stateMixin(Vue: typeof Component) {
|
// flow somehow has problems with directly declared definition object
|
// when using Object.defineProperty, so we have to procedurally build up
|
// the object here.
|
const dataDef: any = {}
|
dataDef.get = function () {
|
return this._data
|
}
|
const propsDef: any = {}
|
propsDef.get = function () {
|
return this._props
|
}
|
if (__DEV__) {
|
dataDef.set = function () {
|
warn(
|
'Avoid replacing instance root $data. ' +
|
'Use nested data properties instead.',
|
this
|
)
|
}
|
propsDef.set = function () {
|
warn(`$props is readonly.`, this)
|
}
|
}
|
Object.defineProperty(Vue.prototype, '$data', dataDef)
|
Object.defineProperty(Vue.prototype, '$props', propsDef)
|
|
Vue.prototype.$set = set
|
Vue.prototype.$delete = del
|
|
Vue.prototype.$watch = function (
|
expOrFn: string | (() => any),
|
cb: any,
|
options?: Record<string, any>
|
): Function {
|
const vm: Component = this
|
if (isPlainObject(cb)) {
|
return createWatcher(vm, expOrFn, cb, options)
|
}
|
options = options || {}
|
options.user = true
|
const watcher = new Watcher(vm, expOrFn, cb, options)
|
if (options.immediate) {
|
const info = `callback for immediate watcher "${watcher.expression}"`
|
pushTarget()
|
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
|
popTarget()
|
}
|
return function unwatchFn() {
|
watcher.teardown()
|
}
|
}
|
}
|