【Vue】【选项API - 状态选项】data

一、data的基础介绍

  • 用途:用于声明组件初始响应式状态的函数

  • 类型

    kotlin 复制代码
    interface ComponentOptions {
      data?(
        this: ComponentPublicInstance,
        vm: ComponentPublicInstance
      ): object
    }
  • data数据

    1)该函数应当返回一个普通的 JavaScript 函数,Vue 会将它转换为响应式对象。

    2)实例创建后,可以通过this.$data访问该响应式对象。

    3)组件实例也代理该数据上所有的属性,因此 this.a 等价于 this.$data.a

  • 注意点

    1)所有会用到的顶层数据属性都应该提前在这个对象中声明,如果该属性的值一开始获取不到,应该使用 undefindnull 值来占位,让Vue知道这个属性是存在的。

    2)虽然理论上可以向 this.$data 添加新属性,但并不推荐这么做。

    3)以 _$ 开头的属性将不会 被组件实例代理,因为它们可能和 Vue 的内置属性、API 方法冲突。你必须以 this.$data._property 的方式访问它们。

    4)推荐返回一个可能改变自身状态的对象,如浏览器 API 原生对象或是带原型的类实例等。理想情况下,返回的对象应是一个纯粹代表组件状态的普通对象。

    5)更新data数据

  • 示例

    javascript 复制代码
    export default {
      data() {
        return {
          a: 1,
          b: 2,
        }
      },
      created() {
        console.log(this.a) // 1
        console.log(this.$data) // { a: 1, b: 2 }
      }
    }

二、data的定义方式

data的定义方式有:

  • 对象:data: {},
  • 函数:data() { return {} },

2.1 vue实例和组件实例定义data的区别

  • vue实例 :定义data 属性即可以是一个对象,也可以是一个函数

  • 组件实例 :定义data 属性,只能是一个函数

    • 直接定义为一个对象,就会得到警告信息: [Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.
    • 警告说明:返回的data应该是一个函数在每一个组件实例中

2.2 组件data定义函数和对象的区别

当前有两个组件实例 componentAcomponentB

  • data定义函数:不同组件的data属性值的对象内存地址并不相同

    如果这两个组件是用函数方式定义data。修改组件 componentA 的data属性值, componentB 的属性值不会受到任何影响

  • data定义对象:不同组件的data属性值共用了同一个内存地址,会导致组件之间相互干扰

    如果这两个组件是用对象方式定义data。修改组件 componentA 的data属性值,会发现 componentB 的属性值也发生的变化

    理由如下: componentAcomponentB 这两个组件的data属性值共用了同一个内存地址

三、data原理和源码分析

3.1 实现原理

3.2 源码分析

源码版本:2.7.14

3.2.1 vue初始化data的代码

源码位置:src\core\instance\state.ts

data的执行时机在beforeCreate之后,created之前,会initState中会调用initData :

scss 复制代码
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) // 调用初始化data函数
  } 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)
  }
}

data的初始化函数 - initData:

主要事情:

1)判断 data 对象的每一个 key,不可以和 props、methods 中的 key 相同。否则会报警告。可以看出优先级是 methods > props > data;

2)给data设置代理,代理vm.data到vm上,就可以通过this.xxx访问data上的属性了;

3)观测data;

scss 复制代码
function initData(vm: Component) {
  let data: any = vm.$options.data
  // 如果是函数,就重新定义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
  // 判断 data 对象的每一个 key,不可以和 props、methods 中的 key 相同。否则会报警告。可以看出优先级是 methods > props > data;
  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) // 给data设置代理,代理vm._data到vm上,就可以通过this.xxx访问_data上的属性了;
    }
  }
  // observe data
  // 观测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()
  }
}

3.2.2 组件在创建时,会进行选项的合并

源码位置:src\core\instance\init.ts

自定义组件会进入mergeOptions进行选项合并

scss 复制代码
export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    const vm: Component = this
    // a uid
    vm._uid = uid++
​
    let startTag, endTag
    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }
​
    // a flag to mark this as a Vue instance without having to do instanceof
    // check
    vm._isVue = true
    // avoid instances from being observed
    vm.__v_skip = true
    // effect scope
    vm._scope = new EffectScope(true /* detached */)
    vm._scope._vm = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options as any)
    } else { // 如果是自定义组件就会进入 mergeOptions 进行选项合并
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (__DEV__) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate', undefined, false /* setContext */)
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
​
    /* istanbul ignore if */
    if (__DEV__ && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }
​
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

3.2.3 自定义组件就会进入 mergeOptions 进行选项合并

源码位置:src\core\util\options.ts

scss 复制代码
/**
 * Merge two option objects into a new one.
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions(
  parent: Record<string, any>,
  child: Record<string, any>,
  vm?: Component | null
): ComponentOptions {
  if (__DEV__) {
    checkComponents(child)
  }
​
  if (isFunction(child)) {
    // @ts-expect-error
    child = child.options
  }
​
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
​
  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }
​
  const options: ComponentOptions = {} as any
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField(key: any) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
​

3.2.4 定义data进行数据校验

源码位置:src\core\util\options.ts

typescript 复制代码
strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): Function | null {
  if (!vm) {
    // 如果自定义组件实例的data使用非函数方式定义,就在控制台打印以下警告
    if (childVal && typeof childVal !== 'function') {
      __DEV__ &&
        warn(
          'The "data" option should be a function ' +
            'that returns a per-instance value in component ' +
            'definitions.',
          vm
        )
​
      return parentVal
    }
    return mergeDataOrFn(parentVal, childVal)
  }
​
  return mergeDataOrFn(parentVal, childVal, vm)
}

参考

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端