state.js 8.2 KB

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