今天我们继续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 = 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
) ;
}
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] ;
if ( process. env. NODE_ENV !== 'production' ) {
if ( methods && hasOwn ( methods, key) ) {
warn ( ` Method " ${ key} " has already been defined as a data property. ` ) ;
}
}
proxy ( vm, ` _data ` , key) ;
}
observe ( data, true ) ;
}
注释说明:
getData(data, vm)
:如果 data
是函数,则执行它获取数据对象。proxy(vm, '_data', key)
:将 data
中的属性代理到 vm
实例上,便于通过 this.key
访问。observe(data, true)
:调用 Observer
类对数据进行响应式劫持。
✅ Vue3 中的 data
实现
源码文件位置:
文件路径:packages/runtime-core/src/componentOptions.ts
函数名:setupStatefulComponent
关键代码片段(简化版):
function setupStatefulComponent ( instance: ComponentInternalInstance) {
const Component = instance. type ;
const { setup } = Component;
initProps ( instance, Component. props) ;
initSlots ( instance, children) ;
if ( setup) {
const setupResult = setup ( shallowReadonly ( instance. props) , instance. setupContext) ;
handleSetupResult ( instance, setupResult) ;
} else {
finishComponentSetup ( instance) ;
}
}
function finishComponentSetup ( instance: ComponentInternalInstance) {
const Component = instance. type ;
if ( Component. data) {
const data = Component. data . call ( instance. proxy) ;
for ( const key in data) {
defineReactive ( instance. data, key, data[ key] ) ;
}
}
}
注释说明:
setup()
:Composition API 入口,优先于 data
。finishComponentSetup()
:当没有 setup()
时才会进入传统 Options API 流程。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 上 使用 Proxy
或 Reflect
实现代理 模块模式 模块化结构(如 core、platforms) 更细粒度模块划分(如 reactivity、runtime-core)
三、10 大高频面试题(含答案)
编号 面试题 答案 1 Vue2 中为什么推荐 data返回一个函数? 防止组件间共享同一个对象引用,避免数据污染。 2 Vue3 中 data和 setup()
如何共存? setup()
优先级更高,若存在则不会处理 data。3 Vue2 的 data是如何变成响应式的? 通过 observe()
对每个属性进行 defineProperty
劫持。 4 Vue3 的 data是如何响应式的? 通过 reactive()
包裹整个对象,或使用 ref()
包裹单个值。 5 Vue3 中是否还支持 data? 支持,但 Composition API 更推荐。 6 Vue3 中 data能否和 setup()
一起使用? 可以,但优先使用 setup()
。 7 Vue2 中 data不能嵌套怎么办? 使用 Vue.set()
或改用数组变异方法。 8 Vue3 中如何监听 data变化? 使用 watch()
或 watchEffect()
。 9 Vue2 中 data是不是必须的? 不是,可以只使用 props。 10 Vue3 中 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()
方法进行响应式处理,依赖 Observer
和 defineProperty
。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 ;
instance. proxy = new Proxy ( instance, PublicInstanceProxyHandlers) ;
const { setup } = Component;
if ( setup) {
const setupContext = createSetupContext ( instance) ;
const setupResult = setup ( shallowReadonly ( instance. props) , setupContext) ;
handleSetupResult ( instance, setupResult) ;
} else {
finishComponentSetup ( instance) ;
}
}
注释说明:
instance.proxy
:创建代理对象,使得在 setup()
中可以通过 this
访问组件实例。createSetupContext()
:创建 setup 上下文,提供 emit
、props
、expose
等功能。setup()
:调用用户定义的 setup()
函数。handleSetupResult()
:处理返回值,支持返回 Object
或 Function
(如渲染函数)。
文件:packages/runtime-core/src/componentCompositionApi.ts
function handleSetupResult ( instance: ComponentInternalInstance, setupResult: unknown ) {
if ( isFunction ( setupResult) ) {
instance. render = setupResult;
} else if ( isObject ( setupResult) ) {
instance. setupState = proxyRefs ( setupResult) ;
}
finishComponentSetup ( instance) ;
}
注释说明:
isFunction(setupResult)
:判断是否是返回的 render 函数。proxyRefs()
:自动解包 ref,使得在模板中可以直接使用响应式变量。finishComponentSetup()
:继续完成组件挂载流程(如注册生命周期钩子、模板编译等)。
六、10 大 Vue3 setup()
面试题(含答案)
编号 面试题 答案 1 setup()
是什么?是 Vue3 Composition API 的入口函数,替代 Vue2 的 data、methods 等选项。 2 setup()
和 data能否共存?可以,但优先使用 setup()
,若存在则不会处理 data。 3 setup()
接收哪些参数?第一个参数是 props(只读),第二个是 setupContext
(包含 emit、expose、attrs)。 4 如何在 setup()
中访问组件实例? 通过 getCurrentInstance()
获取当前组件实例。 5 setup()
中如何监听 props 变化?使用 watchEffect()
或 watch()
监听 props。 6 setup()
是否能有返回值?可以,返回的对象会暴露给模板;也可以返回一个函数作为 render 函数。 7 setup()
中如何触发事件?使用 emit
,它来自 setupContext
。 8 setup()
中能否使用生命周期钩子?可以,需导入 onMounted
、onUpdated
等 Composition API。 9 setup()
和 script setup
的区别?setup()
是显式函数,<script setup>
是语法糖,由编译器自动注入上下文。10 setup()
中如何处理异步逻辑?支持异步,但不能直接返回 Promise,否则会导致模板无法渲染。
七、总结
Vue3 的 setup()
是 Composition API 的核心入口,位于 component.ts
和 componentCompositionApi.ts
中。它接收 props和 setupContext
,并允许返回响应式数据或自定义渲染函数。 在面试中常考点包括:参数传递、响应式机制、生命周期钩子、与 script setup
的关系等。 实际开发中,setup()
更加灵活和模块化,推荐替代 Vue2 的 Options API 写法。