b41c72a1bd9d9ab55910f6e3970beac4c81461eda3b330fbda717e8237dea9f0353fd57e8abb3bf9a130eea3c7400e13557f45f2e789c7ba300a72938083ef 9.9 KB

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