一、data的基础介绍
-
用途:用于声明组件初始响应式状态的函数
-
类型:
kotlininterface ComponentOptions { data?( this: ComponentPublicInstance, vm: ComponentPublicInstance ): object }
-
data数据:
1)该函数应当返回一个普通的 JavaScript 函数,Vue 会将它转换为响应式对象。
2)实例创建后,可以通过
this.$data
访问该响应式对象。3)组件实例也代理该数据上所有的属性,因此
this.a
等价于this.$data.a
。 -
注意点:
1)所有会用到的顶层数据属性都应该提前在这个对象中声明,如果该属性的值一开始获取不到,应该使用
undefind
或null
值来占位,让Vue知道这个属性是存在的。2)虽然理论上可以向
this.$data
添加新属性,但并不推荐这么做。3)以
_
或$
开头的属性将不会 被组件实例代理,因为它们可能和 Vue 的内置属性、API 方法冲突。你必须以this.$data._property
的方式访问它们。4)不推荐返回一个可能改变自身状态的对象,如浏览器 API 原生对象或是带原型的类实例等。理想情况下,返回的对象应是一个纯粹代表组件状态的普通对象。
5)更新data数据
-
示例:
javascriptexport 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定义函数和对象的区别
当前有两个组件实例 componentA
和 componentB
;
-
data定义函数:不同组件的data属性值的对象内存地址并不相同
如果这两个组件是用函数方式定义data。修改组件
componentA
的data属性值,componentB
的属性值不会受到任何影响 -
data定义对象:不同组件的data属性值共用了同一个内存地址,会导致组件之间相互干扰
如果这两个组件是用对象方式定义data。修改组件
componentA
的data属性值,会发现componentB
的属性值也发生的变化理由如下:
componentA
和componentB
这两个组件的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)
}