vue2组件中data为什么必须是个函数而Vue的根实例则没有此限制?

702 阅读2分钟
function initData (vm: Component) {
  // 取到data
  let data = vm.$options.data

  // 如果data的类型是一个函数,那么将其执行 getData() 并将其结果作为data选项的值,否则就是data
  // 此处可以想象一个场景:定义了一个组件comp,这个组件在被声明的时候其实只执行了一次。
  // 如果是直接给data: { count: 1 },每一次初始化的时候组件的实例的data都指向同一个地址
  // 这样就会造成实例污染,大家用的都是同一个data选项,你改一下我改一下岂不是乱了套。
  // 所以vue内部做了处理,如果是组件,那么就只能写data函数,否则会报错。

  // 所以vue为什么不让我们在组件里直接给data定义成对象,是为了避免组件多实例之间的相互污染,所以将其作为工厂函数的形式去定义。

  // 而为什么在根实例里定义data就可以直接定义为对象呢,因为vue2里的data是单例的,只会出现一个,所以就不存在相互污染的可能性。

  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {}
  // 如果data不是一个空对象
  if (!isPlainObject(data)) {
    data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }

结论

Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致他们公用一个data对象,那么状态变更将会影响所有组件实例,这是不合理的;采用函数形式定义,在initData时会将其作为工厂函数返回给全新的data对象,有效规避多实例之前状态污染问题。而在Vue根实例创建过程中则不存在该限制,也是因为根实例只能有一个,不需要担心这种情况的发生。在源码中数据初始化时,发现会检测data的形式从而去执行他的具体的执行方式,另外的话根实例在创建的时候可能在合并选项的时候,他会有实例拿到只有根实例有实例,他可以有效的避免根实例的校验,而一个组件模块在当时可能还没有实例,没法躲过校验的if逻辑,直接会被检测data的类型,所以用户在写代码的时候,其实也没办法在组件中给data定义为对象。