src/core/instance/state.ts TYPESCRIPT 394 lines View on github.com → Search inside
1import config from '../config'2import Watcher from '../observer/watcher'3import Dep, { pushTarget, popTarget } from '../observer/dep'4import { isUpdatingChildComponent } from './lifecycle'5import { initSetup } from 'v3/apiSetup'67import {8  set,9  del,10  observe,11  defineReactive,12  toggleObserving13} from '../observer/index'1415import {16  warn,17  bind,18  noop,19  hasOwn,20  isArray,21  hyphenate,22  isReserved,23  handleError,24  nativeWatch,25  validateProp,26  isPlainObject,27  isServerRendering,28  isReservedAttribute,29  invokeWithErrorHandling,30  isFunction31} from '../util/index'32import type { Component } from 'types/component'33import { shallowReactive, TrackOpTypes } from 'v3'3435const sharedPropertyDefinition = {36  enumerable: true,37  configurable: true,38  get: noop,39  set: noop40}4142export function proxy(target: Object, sourceKey: string, key: string) {43  sharedPropertyDefinition.get = function proxyGetter() {44    return this[sourceKey][key]45  }46  sharedPropertyDefinition.set = function proxySetter(val) {47    this[sourceKey][key] = val48  }49  Object.defineProperty(target, key, sharedPropertyDefinition)50}5152export function initState(vm: Component) {53  const opts = vm.$options54  if (opts.props) initProps(vm, opts.props)5556  // Composition API57  initSetup(vm)5859  if (opts.methods) initMethods(vm, opts.methods)60  if (opts.data) {61    initData(vm)62  } else {63    const ob = observe((vm._data = {}))64    ob && ob.vmCount++65  }66  if (opts.computed) initComputed(vm, opts.computed)67  if (opts.watch && opts.watch !== nativeWatch) {68    initWatch(vm, opts.watch)69  }70}7172function initProps(vm: Component, propsOptions: Object) {73  const propsData = vm.$options.propsData || {}74  const props = (vm._props = shallowReactive({}))75  // cache prop keys so that future props updates can iterate using Array76  // instead of dynamic object key enumeration.77  const keys: string[] = (vm.$options._propKeys = [])78  const isRoot = !vm.$parent79  // root instance props should be converted80  if (!isRoot) {81    toggleObserving(false)82  }83  for (const key in propsOptions) {84    keys.push(key)85    const value = validateProp(key, propsOptions, propsData, vm)86    /* istanbul ignore else */87    if (__DEV__) {88      const hyphenatedKey = hyphenate(key)89      if (90        isReservedAttribute(hyphenatedKey) ||91        config.isReservedAttr(hyphenatedKey)92      ) {93        warn(94          `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`,95          vm96        )97      }98      defineReactive(99        props,100        key,101        value,102        () => {103          if (!isRoot && !isUpdatingChildComponent) {104            warn(105              `Avoid mutating a prop directly since the value will be ` +106                `overwritten whenever the parent component re-renders. ` +107                `Instead, use a data or computed property based on the prop's ` +108                `value. Prop being mutated: "${key}"`,109              vm110            )111          }112        },113        true /* shallow */114      )115    } else {116      defineReactive(props, key, value, undefined, true /* shallow */)117    }118    // static props are already proxied on the component's prototype119    // during Vue.extend(). We only need to proxy props defined at120    // instantiation here.121    if (!(key in vm)) {122      proxy(vm, `_props`, key)123    }124  }125  toggleObserving(true)126}127128function initData(vm: Component) {129  let data: any = vm.$options.data130  data = vm._data = isFunction(data) ? getData(data, vm) : data || {}131  if (!isPlainObject(data)) {132    data = {}133    __DEV__ &&134      warn(135        'data functions should return an object:\n' +136          'https://v2.vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',137        vm138      )139  }140  // proxy data on instance141  const keys = Object.keys(data)142  const props = vm.$options.props143  const methods = vm.$options.methods144  let i = keys.length145  while (i--) {146    const key = keys[i]147    if (__DEV__) {148      if (methods && hasOwn(methods, key)) {149        warn(`Method "${key}" has already been defined as a data property.`, vm)150      }151    }152    if (props && hasOwn(props, key)) {153      __DEV__ &&154        warn(155          `The data property "${key}" is already declared as a prop. ` +156            `Use prop default value instead.`,157          vm158        )159    } else if (!isReserved(key)) {160      proxy(vm, `_data`, key)161    }162  }163  // observe data164  const ob = observe(data)165  ob && ob.vmCount++166}167168export function getData(data: Function, vm: Component): any {169  // #7573 disable dep collection when invoking data getters170  pushTarget()171  try {172    return data.call(vm, vm)173  } catch (e: any) {174    handleError(e, vm, `data()`)175    return {}176  } finally {177    popTarget()178  }179}180181const computedWatcherOptions = { lazy: true }182183function initComputed(vm: Component, computed: Object) {184  // $flow-disable-line185  const watchers = (vm._computedWatchers = Object.create(null))186  // computed properties are just getters during SSR187  const isSSR = isServerRendering()188189  for (const key in computed) {190    const userDef = computed[key]191    const getter = isFunction(userDef) ? userDef : userDef.get192    if (__DEV__ && getter == null) {193      warn(`Getter is missing for computed property "${key}".`, vm)194    }195196    if (!isSSR) {197      // create internal watcher for the computed property.198      watchers[key] = new Watcher(199        vm,200        getter || noop,201        noop,202        computedWatcherOptions203      )204    }205206    // component-defined computed properties are already defined on the207    // component prototype. We only need to define computed properties defined208    // at instantiation here.209    if (!(key in vm)) {210      defineComputed(vm, key, userDef)211    } else if (__DEV__) {212      if (key in vm.$data) {213        warn(`The computed property "${key}" is already defined in data.`, vm)214      } else if (vm.$options.props && key in vm.$options.props) {215        warn(`The computed property "${key}" is already defined as a prop.`, vm)216      } else if (vm.$options.methods && key in vm.$options.methods) {217        warn(218          `The computed property "${key}" is already defined as a method.`,219          vm220        )221      }222    }223  }224}225226export function defineComputed(227  target: any,228  key: string,229  userDef: Record<string, any> | (() => any)230) {231  const shouldCache = !isServerRendering()232  if (isFunction(userDef)) {233    sharedPropertyDefinition.get = shouldCache234      ? createComputedGetter(key)235      : createGetterInvoker(userDef)236    sharedPropertyDefinition.set = noop237  } else {238    sharedPropertyDefinition.get = userDef.get239      ? shouldCache && userDef.cache !== false240        ? createComputedGetter(key)241        : createGetterInvoker(userDef.get)242      : noop243    sharedPropertyDefinition.set = userDef.set || noop244  }245  if (__DEV__ && sharedPropertyDefinition.set === noop) {246    sharedPropertyDefinition.set = function () {247      warn(248        `Computed property "${key}" was assigned to but it has no setter.`,249        this250      )251    }252  }253  Object.defineProperty(target, key, sharedPropertyDefinition)254}255256function createComputedGetter(key) {257  return function computedGetter() {258    const watcher = this._computedWatchers && this._computedWatchers[key]259    if (watcher) {260      if (watcher.dirty) {261        watcher.evaluate()262      }263      if (Dep.target) {264        if (__DEV__ && Dep.target.onTrack) {265          Dep.target.onTrack({266            effect: Dep.target,267            target: this,268            type: TrackOpTypes.GET,269            key270          })271        }272        watcher.depend()273      }274      return watcher.value275    }276  }277}278279function createGetterInvoker(fn) {280  return function computedGetter() {281    return fn.call(this, this)282  }283}284285function initMethods(vm: Component, methods: Object) {286  const props = vm.$options.props287  for (const key in methods) {288    if (__DEV__) {289      if (typeof methods[key] !== 'function') {290        warn(291          `Method "${key}" has type "${typeof methods[292            key293          ]}" in the component definition. ` +294            `Did you reference the function correctly?`,295          vm296        )297      }298      if (props && hasOwn(props, key)) {299        warn(`Method "${key}" has already been defined as a prop.`, vm)300      }301      if (key in vm && isReserved(key)) {302        warn(303          `Method "${key}" conflicts with an existing Vue instance method. ` +304            `Avoid defining component methods that start with _ or $.`305        )306      }307    }308    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)309  }310}311312function initWatch(vm: Component, watch: Object) {313  for (const key in watch) {314    const handler = watch[key]315    if (isArray(handler)) {316      for (let i = 0; i < handler.length; i++) {317        createWatcher(vm, key, handler[i])318      }319    } else {320      createWatcher(vm, key, handler)321    }322  }323}324325function createWatcher(326  vm: Component,327  expOrFn: string | (() => any),328  handler: any,329  options?: Object330) {331  if (isPlainObject(handler)) {332    options = handler333    handler = handler.handler334  }335  if (typeof handler === 'string') {336    handler = vm[handler]337  }338  return vm.$watch(expOrFn, handler, options)339}340341export function stateMixin(Vue: typeof Component) {342  // flow somehow has problems with directly declared definition object343  // when using Object.defineProperty, so we have to procedurally build up344  // the object here.345  const dataDef: any = {}346  dataDef.get = function () {347    return this._data348  }349  const propsDef: any = {}350  propsDef.get = function () {351    return this._props352  }353  if (__DEV__) {354    dataDef.set = function () {355      warn(356        'Avoid replacing instance root $data. ' +357          'Use nested data properties instead.',358        this359      )360    }361    propsDef.set = function () {362      warn(`$props is readonly.`, this)363    }364  }365  Object.defineProperty(Vue.prototype, '$data', dataDef)366  Object.defineProperty(Vue.prototype, '$props', propsDef)367368  Vue.prototype.$set = set369  Vue.prototype.$delete = del370371  Vue.prototype.$watch = function (372    expOrFn: string | (() => any),373    cb: any,374    options?: Record<string, any>375  ): Function {376    const vm: Component = this377    if (isPlainObject(cb)) {378      return createWatcher(vm, expOrFn, cb, options)379    }380    options = options || {}381    options.user = true382    const watcher = new Watcher(vm, expOrFn, cb, options)383    if (options.immediate) {384      const info = `callback for immediate watcher "${watcher.expression}"`385      pushTarget()386      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)387      popTarget()388    }389    return function unwatchFn() {390      watcher.teardown()391    }392  }393}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.