【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)
}

参考

相关推荐
laocooon52385788619 分钟前
HTML CSS 超链
前端·css·html
LUwantAC21 分钟前
CSS(二):美化网页元素
前端·css
m0_7482510833 分钟前
docker安装nginx,docker部署vue前端,以及docker部署java的jar部署
java·前端·docker
我是ed1 小时前
# thingjs 基础案例整理
前端
Ashore_1 小时前
从简单封装到数据响应:Vue如何引领开发新模式❓❗️
前端·vue.js
落魄实习生1 小时前
小米路由器开启SSH,配置阿里云ddns,开启外网访问SSH和WEB管理界面
前端·阿里云·ssh
顽疲1 小时前
从零用java实现 小红书 springboot vue uniapp (6)用户登录鉴权及发布笔记
java·vue.js·spring boot·uni-app
bug丸1 小时前
v8引擎垃圾回收
前端·javascript·垃圾回收
安全小王子1 小时前
攻防世界web第三题file_include
前端
&活在当下&1 小时前
ref 和 reactive 的用法和区别
前端·javascript·vue.js