【vue源码03】深入学习vue源码实现——data与setup方法的底层实现

今天我们继续vue的源码学习,我将从源码实现、设计模式、高频面试题和应用场景四个方面详细解析 Vue2 和 Vue3 中 data与setup的底层实现,以及setup是如何做到更高效地处理数据的。


一、Vue2 和 Vue3 的 data底层源码实现

✅ Vue2 中的 data实现

源码文件位置:
  • 文件路径:src/core/instance/state.js
  • 函数名:initData
关键代码片段(简化版):
function initData(vm: Component) {
  let data = vm.$options.data;

  // 如果 data 是函数,则调用 getData 获取返回对象
  data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {};

  if (!isPlainObject(data)) {
    data = {};
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/guide/components.html#data-Must-Be-a-Function',
      vm
    );
  }

  // 将 data 属性代理到 vm 上,使得 this.xxx 可以访问
  const keys = Object.keys(data);
  const props = vm.$options.props;
  const methods = vm.$options.methods;
  let i = keys.length;

  while (i--) {
    const key = keys[i];

    // 避免与 props、methods 冲突
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(`Method "${key}" has already been defined as a data property.`);
      }
    }

    // 代理 data 到 vm 上
    proxy(vm, `_data`, key);
  }

  // 使用 Observer 对 data 进行响应式处理
  observe(data, true /* asRootData */);
}
注释说明:
  1. getData(data, vm):如果 data 是函数,则执行它获取数据对象。
  2. proxy(vm, '_data', key):将 data 中的属性代理到 vm 实例上,便于通过 this.key 访问。
  3. observe(data, true):调用 Observer 类对数据进行响应式劫持。

✅ Vue3 中的 data 实现

源码文件位置:
  • 文件路径:packages/runtime-core/src/componentOptions.ts
  • 函数名:setupStatefulComponent
关键代码片段(简化版):
function setupStatefulComponent(instance: ComponentInternalInstance) {
  const Component = instance.type;
  const { setup } = Component;

  // 初始化 props、slots 等
  initProps(instance, Component.props);
  initSlots(instance, children);

  // 如果有 setup,则优先使用 Composition API
  if (setup) {
    const setupResult = setup(shallowReadonly(instance.props), instance.setupContext);
    handleSetupResult(instance, setupResult);
  } else {
    // 否则走 Options API 流程
    finishComponentSetup(instance);
  }
}

function finishComponentSetup(instance: ComponentInternalInstance) {
  const Component = instance.type;

  // 如果没有 setup,则初始化 data、computed、watch 等
  if (Component.data) {
    const data = Component.data.call(instance.proxy);
    for (const key in data) {
      defineReactive(instance.data, key, data[key]);
    }
  }
}
注释说明:
  1. setup():Composition API 入口,优先于 data
  2. finishComponentSetup():当没有 setup() 时才会进入传统 Options API 流程。
  3. defineReactive():对 data 属性进行响应式处理(基于 Proxy/Object.defineProperty)。

二、设计模式对比

设计模式Vue2 实现Vue3 实现
观察者模式数据变化通知 Watcher 更新视图基于 effect + track + trigger 构建响应式系统
工厂模式通过 new Vue() 创建实例通过 createApp() 工厂函数创建应用
策略模式根据 data是否为函数选择不同处理逻辑支持 Composition API 与 Options API 两种策略
代理模式使用 proxy(vm, '_data', key) 将 data 属性代理到 this 上使用 ProxyReflect 实现代理
模块模式模块化结构(如 core、platforms)更细粒度模块划分(如 reactivity、runtime-core)

三、10 大高频面试题(含答案)

编号面试题答案
1Vue2 中为什么推荐 data返回一个函数?防止组件间共享同一个对象引用,避免数据污染。
2Vue3 中 data和 setup() 如何共存?setup() 优先级更高,若存在则不会处理 data。
3Vue2 的 data是如何变成响应式的?通过 observe() 对每个属性进行 defineProperty 劫持。
4Vue3 的 data是如何响应式的?通过 reactive() 包裹整个对象,或使用 ref() 包裹单个值。
5Vue3 中是否还支持 data?支持,但 Composition API 更推荐。
6Vue3 中 data能否和 setup() 一起使用?可以,但优先使用 setup()
7Vue2 中 data不能嵌套怎么办?使用 Vue.set() 或改用数组变异方法。
8Vue3 中如何监听 data变化?使用 watch()watchEffect()
9Vue2 中 data是不是必须的?不是,可以只使用 props。
10Vue3 中 data 是否还能被代理?是的,依然支持通过 this 访问 data属性。

四、10 大典型应用场景

场景编号应用场景描述Vue2 示例Vue3 示例
1表单状态管理在 data中维护表单项的值使用 ref()reactive() 维护表单状态
2列表渲染在 data中维护列表数组使用 ref([])reactive([])
3组件通信中的本地状态使用 data 存储组件内部状态使用 ref() 存储局部状态
4条件渲染控制用 data控制 v-if 显示隐藏使用 ref<boolean> 控制显示
5表格分页信息维护用 data存储当前页码、每页条数等使用 reactive({ page: 1, pageSize: 10 })
6定时器状态管理使用 data.timerId 存储定时器 ID使用 ref<number>(0) 存储 timerId
7表单验证状态在 data 中保存校验结果使用 ref<ValidationResult>()
8弹窗显隐控制showModal 控制弹窗使用 ref(false) 控制
9用户登录状态存储isLoggedIn 控制 UI使用 ref(true)
10页面加载状态loading 控制 loading 状态使用 ref(true)

总结

  • Vue2 的 data是在组件初始化阶段通过 initData() 方法进行响应式处理,依赖 ObserverdefineProperty
  • Vue3 的 data 作为 Options API 的一部分,仍支持使用,但在 Composition API 下优先使用 ref / reactive
  • 设计模式 上 Vue3 更加现代化,支持更灵活的状态管理和组合方式。
  • 面试题与应用场景 主要围绕响应式原理、生命周期、最佳实践等方面展开。

五、Vue3 的 setup() 源码实现(含完整注释)

✅ 源码位置

  • 文件路径:packages/runtime-core/src/component.ts
  • 主要函数:setupStatefulComponent()
  • 辅助函数:handleSetupResult()

✅ 核心源码与注释(简化版)

文件:packages/runtime-core/src/component.ts
function setupStatefulComponent(instance: ComponentInternalInstance) {
  const Component = instance.type;

  // 创建组件的 proxy,用于 this 访问
  instance.proxy = new Proxy(instance, PublicInstanceProxyHandlers);

  // 获取 setup 函数
  const { setup } = Component;

  if (setup) {
    // 创建 setupContext,包含 emit、props、expose 等
    const setupContext = createSetupContext(instance);

    // 调用 setup 函数,传入 props 和 context
    const setupResult = setup(shallowReadonly(instance.props), setupContext);

    // 处理 setup 返回的结果
    handleSetupResult(instance, setupResult);
  } else {
    // 如果没有 setup,则进入 Options API 流程
    finishComponentSetup(instance);
  }
}
注释说明:
  1. instance.proxy:创建代理对象,使得在 setup() 中可以通过 this 访问组件实例。
  2. createSetupContext():创建 setup 上下文,提供 emitpropsexpose 等功能。
  3. setup():调用用户定义的 setup() 函数。
  4. handleSetupResult():处理返回值,支持返回 ObjectFunction(如渲染函数)。

文件:packages/runtime-core/src/componentCompositionApi.ts
function handleSetupResult(instance: ComponentInternalInstance, setupResult: unknown) {
  if (isFunction(setupResult)) {
    // 如果 setup 返回的是函数,作为 render 函数使用
    instance.render = setupResult;
  } else if (isObject(setupResult)) {
    // 如果返回的是对象,将其暴露为 setupContext.return
    instance.setupState = proxyRefs(setupResult);
  }

  // 继续初始化模板编译或渲染流程
  finishComponentSetup(instance);
}
注释说明:
  1. isFunction(setupResult):判断是否是返回的 render 函数。
  2. proxyRefs():自动解包 ref,使得在模板中可以直接使用响应式变量。
  3. finishComponentSetup():继续完成组件挂载流程(如注册生命周期钩子、模板编译等)。

六、10 大 Vue3 setup() 面试题(含答案)

编号面试题答案
1setup() 是什么?是 Vue3 Composition API 的入口函数,替代 Vue2 的 data、methods 等选项。
2setup() 和 data能否共存?可以,但优先使用 setup(),若存在则不会处理 data。
3setup() 接收哪些参数?第一个参数是 props(只读),第二个是 setupContext(包含 emit、expose、attrs)。
4如何在 setup() 中访问组件实例?通过 getCurrentInstance() 获取当前组件实例。
5setup() 中如何监听 props 变化?使用 watchEffect()watch() 监听 props。
6setup() 是否能有返回值?可以,返回的对象会暴露给模板;也可以返回一个函数作为 render 函数。
7setup() 中如何触发事件?使用 emit,它来自 setupContext
8setup() 中能否使用生命周期钩子?可以,需导入 onMountedonUpdated 等 Composition API。
9setup()script setup 的区别?setup() 是显式函数,<script setup> 是语法糖,由编译器自动注入上下文。
10setup() 中如何处理异步逻辑?支持异步,但不能直接返回 Promise,否则会导致模板无法渲染。

七、总结

  • Vue3 的 setup() 是 Composition API 的核心入口,位于 component.tscomponentCompositionApi.ts 中。
  • 它接收 props和 setupContext,并允许返回响应式数据或自定义渲染函数。
  • 在面试中常考点包括:参数传递、响应式机制、生命周期钩子、与 script setup 的关系等。
  • 实际开发中,setup() 更加灵活和模块化,推荐替代 Vue2 的 Options API 写法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值