state.js 9.5 KB

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