文章目录
vue3 中 ref 和 reactive
- ref原理
- 基本原理
ref
是Vue 3中用于创建响应式数据的一个函数。它的基本原理是通过Object.defineProperty()
(在JavaScript的规范中用于定义对象属性的方法)或者Proxy
(ES6新特性,用于创建对象的代理)来实现数据的响应式。当数据被修改时,它会触发更新相关的副作用函数(例如更新DOM等操作)。- 当使用
ref
创建一个响应式数据时,它实际上返回一个包含value
属性的对象。例如,const count = ref(0)
,这里的count
是一个对象,访问和修改数据需要通过count.value
。
- 底层实现细节(简单示例)
- 以下是一个简单的模拟
ref
实现的代码(简化版,实际Vue 3代码更复杂):
- 以下是一个简单的模拟
- 基本原理
function ref(raw) {
const r = {
get value() {
track(r, 'value');
return raw;
},
set value(newVal) {
if (newVal!== raw) {
raw = newVal;
trigger(r, 'value');
}
}
};
return r;
}
- 这里的`track`函数用于收集依赖(记录哪些函数依赖了这个`ref`数据),`trigger`函数用于触发更新(当数据改变时,通知依赖的函数重新执行)。
- reactive原理
- 基本原理
reactive
是基于Proxy
对象来实现响应式的。Proxy
可以拦截对目标对象的各种操作,如读取属性、设置属性、删除属性等。当这些操作发生时,reactive
可以触发相应的更新逻辑。- 例如,
const state = reactive({ count: 0 })
,对state.count
的读取和修改都会被Proxy
拦截,从而实现响应式。
- 底层实现细节(简单示例)
- 以下是一个简单的模拟
reactive
实现的代码片段(简化版):
- 以下是一个简单的模拟
- 基本原理
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key);
return result;
}
});
}
- 同样,这里的`track`和`trigger`函数用于收集依赖和触发更新,和`ref`的原理类似,但是`reactive`是针对整个对象的属性进行拦截。
-
ref和reactive的应用场景
- ref应用场景
- 基本数据类型:当处理基本数据类型(如
number
、string
、boolean
)的响应式数据时,通常使用ref
。例如,一个计数器const count = ref(0)
,这里count
是一个ref
对象,通过count.value
来访问和修改计数值。 - 在组合式API中传递单个数据:如果在组合式API函数之间传递一个简单的响应式数据,
ref
是一个很好的选择。比如一个函数返回一个ref
数据,另一个函数可以接收并使用这个ref
。
- 基本数据类型:当处理基本数据类型(如
- reactive应用场景
- 复杂对象类型:对于复杂的对象(如包含多个属性的对象或数组),使用
reactive
更为合适。例如,const state = reactive({ name: 'John', age: 30, hobbies: ['reading', 'coding']})
,对state
对象中的任何属性的访问和修改都会是响应式的。 - 多个相关数据的管理:当需要管理一组相关的数据并且希望它们作为一个整体进行响应式处理时,
reactive
是首选。比如一个表单数据对象,包含表单的各个字段值,使用reactive
可以方便地处理整个表单数据的更新和响应。
- 复杂对象类型:对于复杂的对象(如包含多个属性的对象或数组),使用
- ref应用场景
-
应用要求/依据
- 响应式数据的修改原则
- 对于
ref
,必须通过.value
属性来修改数据才能触发响应式更新。例如,count.value++
会触发更新,而直接count = new_value
(这里count
是ref
)不会触发响应式更新。 - 对于
reactive
,直接修改对象的属性就可以触发响应式更新。如state.name = 'Jane'
(这里state
是reactive
对象)会触发更新。
- 对于
- 模板中的使用差异
- 在Vue 3的模板中,
ref
的数据可以直接在模板中使用,不需要额外的.value
语法。例如,<div>{{ count }}</div>
(这里count
是ref
)是可以正常工作的,Vue会自动处理value
属性的访问。而对于reactive
对象,直接通过属性访问即可,如<div>{{ state.name }}</div>
(这里state
是reactive
对象)。
- 在Vue 3的模板中,
- 响应式数据的修改原则
reactive 与 ref 不同之处
- 数据类型处理方式不同
- ref
- 主要用于包装基本数据类型(如
number
、string
、boolean
),使其具有响应式特性。例如const count = ref(0)
,count
是一个对象,它有一个value
属性来存储实际的数据。在JavaScript代码中,必须通过count.value
来访问和修改这个数据。
- 主要用于包装基本数据类型(如
- reactive
- 用于处理复杂的对象类型(如对象字面量或数组),它会递归地将对象的所有属性转换为响应式。例如
const state = reactive({ count: 0, message: 'Hello'})
,state
本身就是响应式对象,对state.count
和state.message
等属性的访问和修改都是响应式的,不需要像ref
那样通过特定的value
属性来操作。
- 用于处理复杂的对象类型(如对象字面量或数组),它会递归地将对象的所有属性转换为响应式。例如
- ref
- 在模板中的使用差异
- ref
- 在Vue 3模板中使用
ref
时,不需要显式地访问value
属性。Vue会自动进行解包,例如,如果count
是一个ref
,在模板中可以直接写成<div>{{ count }}</div>
,它会正确地渲染count.value
的值。
- 在Vue 3模板中使用
- reactive
- 对于
reactive
对象,在模板中直接通过属性访问来使用。如const state = reactive({ name: 'John'})
,在模板中可以写成<div>{{ state.name }}</div>
来显示name
属性的值。
- 对于
- ref
- 响应式转换的深度不同
- ref
- 只对它包装的那个数据本身进行响应式处理。如果
ref
包装了一个对象,它不会自动将对象内部的属性也转换为响应式,仍然需要通过value
属性来访问对象,并且如果要使对象内部属性具有响应式,可能还需要进一步的处理(如使用reactive
来处理这个对象)。
- 只对它包装的那个数据本身进行响应式处理。如果
- reactive
- 是深度响应式的。当使用
reactive
处理一个对象时,它不仅会使对象本身的属性具有响应式,还会递归地将对象内部嵌套的对象和数组的属性也转换为响应式。例如,如果有一个对象const user = reactive({ name: 'Alice', address: { city: 'New York', street: '123 Main St' }})
,那么user.address.city
的访问和修改也是响应式的。
- 是深度响应式的。当使用
- ref
- 创建和使用的语法及场景差异
- ref
- 创建语法简单直接,适用于单个数据值的响应式处理,特别是基本数据类型。在组合式API中,当需要从一个函数返回一个响应式数据,并且这个数据是基本类型或者只需要简单的包装时,
ref
是很好的选择。
- 创建语法简单直接,适用于单个数据值的响应式处理,特别是基本数据类型。在组合式API中,当需要从一个函数返回一个响应式数据,并且这个数据是基本类型或者只需要简单的包装时,
- reactive
- 创建时需要传入一个对象,更适合处理复杂的、有多个相关属性的数据结构,如应用程序中的状态对象。当有一组相关的数据需要作为一个整体进行响应式管理时,如表单数据、应用的全局状态等,
reactive
是更合适的方式。
- 创建时需要传入一个对象,更适合处理复杂的、有多个相关属性的数据结构,如应用程序中的状态对象。当有一组相关的数据需要作为一个整体进行响应式管理时,如表单数据、应用的全局状态等,
- ref
ref 处理复杂类型
-
ref处理复杂类型
- 从功能上来说,
ref
可以用来包装复杂的数据类型,如对象或数组。例如,可以使用ref
创建一个包含多个属性的对象,const myObject = ref({name: 'John', age: 30})
。
- 从功能上来说,
-
限制
- 访问方式的限制
- 在JavaScript代码中,必须通过
value
属性来访问和修改包装后的复杂对象。例如,要访问上述myObject
中的name
属性,需要使用myObject.value.name
。这与reactive
不同,reactive
可以直接通过属性访问(如const myReactiveObject = reactive({name: 'Alice'}); myReactiveObject.name
)。这种访问方式的差异可能会导致代码不够简洁直观,尤其是在处理多层嵌套的复杂对象时。
- 在JavaScript代码中,必须通过
- 响应式转换深度的限制
ref
本身不会对包装的复杂对象内部进行深度的响应式转换。例如,如果有一个ref
包装的对象const user = ref({name: 'Bob', address: {city: 'New York'}})
,修改user.value.address.city
不会自动触发响应式更新,除非address
这个对象本身也经过了响应式处理(如使用reactive
来处理address
对象)。而reactive
会自动对对象内部进行深度响应式处理。
- 在模板中的限制(部分)
- 在Vue 3的模板中,
ref
包装的复杂对象在某些情况下可能会引起混淆。虽然Vue会自动展开ref
的值,但对于复杂对象的属性访问,有时候可能需要注意绑定的语法。例如,如果要在模板中使用myObject
中的name
属性,写成{{myObject.name}}
是不行的,需要写成{{myObject.value.name}}
,不过Vue在模板渲染时会自动处理ref
的value
属性,使得在大多数简单情况下可以像使用普通数据一样使用ref
包装的数据。但在复杂的模板绑定场景下,还是要注意正确的语法。
- 在Vue 3的模板中,
- 访问方式的限制