前言 在 Vue3 中,响应式系统是其核心特性之一。Vue3 提供了两种创建响应式数据的主要方式:reactive
和 ref
。虽然它们都能实现响应式,但在使用方式、适用场景和底层实现上存在显著差异。本文将深入探讨这两种 API 的区别与原理,帮助开发者更好地理解和使用它们。
如果你对 Vue3 的其他方面也感兴趣,可以看看我们之前的文章,比如 Vue2 VS Vue3 内容全部重构 从各个维度做对比 。
什么是 reactive? reactive
是 Vue3 中用于创建响应式对象的 API。它基于 ES6 的 Proxy 实现,可以拦截对象的各种操作(如获取、设置、删除等)。
reactive 的特点
用于创建一个响应式的对象(包括数组和复杂数据结构)
基于 ES6 的 Proxy 实现
只能用于对象类型(Object、Array、Map、Set 等),不能用于基本类型
直接返回一个响应式对象,无需通过 .value
访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { reactive } from "vue" ;const state = reactive ({ count : 0 , name : "Vue3" , nested : { age : 1 , }, }); console .log (state.count ); state.count ++; console .log (state.count );
什么是 ref? ref
是 Vue3 中用于创建响应式数据的另一种 API,它可以用于任何类型的数据,包括基本类型和对象类型。
ref 的特点
用于创建一个响应式的数据,可以是任意类型(包括基本类型和对象类型)
对于基本类型,ref 使用对象的属性访问器(getter 和 setter)来实现响应式
对于对象类型,ref 内部会调用 reactive 来转换为响应式
ref 返回一个响应式对象,该对象有一个 .value
属性,通过该属性访问和修改值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { ref } from "vue" ;const count = ref (0 );console .log (count.value ); count.value ++; console .log (count.value ); const state = ref ({ name : "Vue3" , version : 3 , }); console .log (state.value .name ); state.value .name = "Vue3.0" ;
核心区别概览
特性
reactive
ref
数据类型
对象类型 (Object, Array, Map, Set)
任意类型 (包括基本类型)
访问方式
直接访问属性
通过 .value 访问
响应式原理
Proxy 代理
对象包装 + getter/setter
TS 类型支持
保持原类型
需要 .value 类型
解构响应式
会丢失响应式
保持响应式
底层原理深度解析 reactive 实现原理 reactive
基于 ES6 的 Proxy 实现。Vue3 使用 Proxy 来包装目标对象,通过 Handler(通常为 baseHandlers)来拦截对目标对象的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 function reactive (target ) { if (!isObject (target)) { return target; } if (target[ReactiveFlags .RAW ]) { return target; } return createReactiveObject ( target, mutableHandlers, mutableCollectionHandlers ); } function createReactiveObject (target, baseHandlers, collectionHandlers ) { const handlers = target instanceof Map || target instanceof Set ? collectionHandlers : baseHandlers; return new Proxy (target, handlers); } const mutableHandlers = { get (target, key, receiver ) { track (target, key); const res = Reflect .get (target, key, receiver); if (isObject (res)) { return reactive (res); } return res; }, set (target, key, value, receiver ) { const oldValue = target[key]; const result = Reflect .set (target, key, value, receiver); if (hasChanged (value, oldValue)) { trigger (target, key); } return result; }, deleteProperty (target, key ) { const hadKey = hasOwn (target, key); const result = Reflect .deleteProperty (target, key); if (hadKey && result) { trigger (target, key); } return result; }, };
当读取属性时,会触发 getter,进行依赖收集(track);当设置属性时,会触发 setter,进行触发更新(trigger)。同时,对于嵌套对象,会在 getter 中递归地调用 reactive,使得整个对象都是响应式的。
实际使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 export const useVideoStore = ( ) => { const videoState = reactive ({ currentVideo : null , playlist : [], playerConfig : { autoplay : true , volume : 0.8 , playbackRate : 1.0 , }, analytics : { watchTime : 0 , interactions : [], }, }); const updateVideo = (video ) => { videoState.currentVideo = video; videoState.analytics .watchTime = 0 ; }; const addToPlaylist = (video ) => { videoState.playlist .push (video); }; return { videoState, updateVideo, addToPlaylist }; };
ref 实现原理 ref
通过一个包装对象来存储值。如果传入的是基本类型,则直接存储;如果是对象类型,则用 reactive 转换后存储。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class RefImpl { constructor (value ) { this ._value = value; this ._rawValue = value; this .dep = undefined ; this .__v_isRef = true ; this ._value = isObject (value) ? reactive (value) : value; } get value () { trackRefValue (this ); return this ._value ; } set value (newVal ) { if (hasChanged (newVal, this ._rawValue )) { this ._rawValue = newVal; this ._value = isObject (newVal) ? reactive (newVal) : newVal; triggerRefValue (this ); } } } function ref (value ) { return new RefImpl (value); } function trackRefValue (ref ) { if (isTracking ()) { trackEffects (ref.dep || (ref.dep = createDep ())); } } function triggerRefValue (ref ) { if (ref.dep ) { triggerEffects (ref.dep ); } }
在访问 ref 的值时,通过 .value
属性来获取,此时会触发 getter,进行依赖收集;当修改 .value
时,会触发 setter,进行触发更新。
实际使用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 export const usePlayerController = ( ) => { const currentTime = ref (0 ); const duration = ref (0 ); const isPlaying = ref (false ); const volume = ref (0.8 ); const playerState = ref ({ status : "idle" , buffered : 0 , error : null , }); const play = ( ) => { isPlaying.value = true ; playerState.value .status = "playing" ; }; const pause = ( ) => { isPlaying.value = false ; playerState.value .status = "paused" ; }; const seek = (time ) => { currentTime.value = time; videoElement.currentTime = time; }; const progress = computed (() => { if (duration.value === 0 ) return 0 ; return (currentTime.value / duration.value ) * 100 ; }); return { currentTime, duration, isPlaying, volume, playerState, progress, play, pause, seek, }; };
简单实现示例 下面我们通过代码来更深入地理解 reactive
和 ref
的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 function reactive (target ) { if (typeof target !== "object" || target === null ) { return target; } const handler = { get (obj, key, receiver ) { const res = Reflect .get (obj, key, receiver); track (obj, key); if (typeof res === "object" && res !== null ) { return reactive (res); } return res; }, set (obj, key, value, receiver ) { const oldValue = obj[key]; const result = Reflect .set (obj, key, value, receiver); if (oldValue !== value) { trigger (obj, key); } return result; }, }; return new Proxy (target, handler); } function ref (value ) { return new RefImpl (value); } class RefImpl { constructor (value ) { this ._value = convert (value); } get value () { track (this , "value" ); return this ._value ; } set value (newVal ) { if (newVal !== this ._value ) { this ._value = convert (newVal); trigger (this , "value" ); } } } function convert (value ) { if (typeof value === "object" && value !== null ) { return reactive (value); } return value; } const targetMap = new WeakMap ();let activeEffect = null ;function track (target, key ) { if (activeEffect) { let depsMap = targetMap.get (target); if (!depsMap) { depsMap = new Map (); targetMap.set (target, depsMap); } let dep = depsMap.get (key); if (!dep) { dep = new Set (); depsMap.set (key, dep); } dep.add (activeEffect); } } function trigger (target, key ) { const depsMap = targetMap.get (target); if (!depsMap) return ; const dep = depsMap.get (key); if (dep) { dep.forEach ((effect ) => effect ()); } } const state = reactive ({ count : 0 });const countRef = ref (0 );function effect (fn ) { activeEffect = fn; fn (); activeEffect = null ; } effect (() => { console .log ("reactive count:" , state.count ); }); effect (() => { console .log ("ref count:" , countRef.value ); }); state.count ++; countRef.value ++;
在上面的代码中,我们简单模拟了 reactive
和 ref
的实现。需要注意的是,Vue3 中的实际实现更加复杂,包括处理数组、Map、Set 等数据结构,以及优化性能等。
面试回答要点
reactive
基于 Proxy,ref
基于 getter/setter 和 reactive
reactive
用于对象,ref
用于任何类型
ref
通过 .value
访问,reactive
直接访问
两者都进行了依赖收集和触发更新
reactive
不能直接用于基本类型,ref
可以
reactive
对象解构会失去响应性,ref
可以保持响应性
总结 reactive
和 ref
是 Vue3 响应式系统的核心 API,它们各有特点和适用场景:
当需要创建一个响应式的对象(Object、Array 等)时,使用 reactive
当需要创建一个响应式的任意类型值时,使用 ref
在模板中,reactive
对象可以直接访问属性,而 ref
需要通过 .value
访问(但在 setup 返回时会自动解包)
在组合函数中,推荐使用 ref
,因为它可以保持响应性即使在解构后
希望这些解释和代码能帮助你更好地理解 Vue3 中 reactive
和 ref
的区别与原理。