reactive 相关
reactive 相关
reactive
函数是 Vue3 中用于创建响应式数据的方法之一,也是 Vue3 响应式系统的核心功能之一,下面我们将探究 reactive
函数的用法及实现原理。
使用方法
reactive
reactive
函数在 Vue3 中的作用是将非原始值转化为响应式数据,它的用法非常简单,只需要传入一个非原始值作为参数即可。
import { reactive } from 'vue'
const state = reactive({count: 0}) //state便是响应式数据了
const arr = reactive([1, 2, 3]) //arr也是响应式数据了
// 错误用法:不能将原始值转化为响应式数据
const num = reactive(1) //产生警告:value cannot be made reactive: 1
shallowReactive
shallowReactive
函数的作用与 reactive
类似,都是将非原始值转化为响应式数据,但是它与 reactive
不同的是,shallowReactive
只会处理对象的第一层属性,而不会处理对象的深层属性。
import { shallowReactive } from 'vue'
//state的count属性是响应式的,state.info不是响应式的
const state = shallowReactive({count: 0, info: {name: 'John'}})
isReactive
isReactive
函数的作用是判断一个对象是否是响应式数据,它的用法非常简单,只需要传入一个对象作为参数即可。
import { isReactive } from 'vue'
const state = reactive({count: 0})
const arr = reactive([1, 2, 3])
console.log(isReactive(state)) //true
console.log(isReactive(arr)) //true
console.log(isReactive({})) //false
readonly
readonly
函数的作用是将一个对象转化为只读的,它的用法非常简单,只需要传入一个对象作为参数即可。
import { readonly } from 'vue'
const state = reactive({count: 0})
const arr = reactive([1, 2, 3])
const stateReadonly = readonly(state)
const arrReadonly = readonly(arr)
console.log(stateReadonly.count) //0
console.log(arrReadonly[0]) //1
stateReadonly.count = 1 //产生警告:Set operation on key "count" failed: target is readonly.
arrReadonly[0] = 2 //产生警告:Set operation on key "0" failed: target is readonly.
shallowReadonly
shallowReadonly
函数的作用与 isReadonly
类似,都是判断一个对象是否是只读的,但是它与 isReadonly
不同的是,shallowReadonly
只会处理对象的第一层属性,而不会处理对象的深层属性。
import { shallowReadonly } from 'vue'
//state的count属性是只读的,不能进行修改操作,state.info不会被转换成只读的,可以修改
const state = shallowReadonly({count: 0, info: {name: 'John'}})
state.count = 1 //产生警告:Set operation on key "count" failed: target is readonly.
state.info.name = 'Tom' //不会产生警告
isReadonly
isReadonly
函数的作用是判断一个对象是否是只读的,它的用法非常简单,只需要传入一个对象作为参数即可。
import { isReadonly } from 'vue'
const state = reactive({count: 0})
const arr = reactive([1, 2, 3])
console.log(isReadonly(state)) //false
console.log(isReadonly(arr)) //false
实现原理
reactive
函数的实现原理是基于 Proxy 对象,通过 Proxy 可以拦截代理对象的基本操作,从而实现响应式数据。(响应式原理与 Proxy 在 Vue3 响应系统 这一章节有过介绍)
代理对象
在 reactive
函数中,首先会创建一个 Proxy 对象,用于拦截对象的基本操作。
function reactive(target) {
return new Proxy(target, { ... })
}
拦截基本操作
reactive
函数在 Proxy 代理对象的配置中,会拦截对代理对象的基本操作,并触发副作用函数执行,它拦截的基本操作有以下几项:
- 获取属性值(get)
- 设置属性值(set)
- 删除属性(deleteProperty)
- 遍历属性(ownKeys)
- 判断属性是否存在(has)
为了实现 reactive
函数及其相关函数的功能,Vue3 为代理对象设置了一些额外的标记:
__v_isReactive
:用于标记代理对象是响应式数据__v_isReadonly
:用于标记代理对象是只读的__v_isShallow
:用于标记代理对象是浅层的__v_raw
:用于标记原始对象
以下是简化版的实现代码:
function createReactive(obj,isShallow = false,isReadonly = false) {
return new Proxy(obj, {
// 拦截获取属性值的操作
get(target, key, receiver) {
//代理对象可以通过raw属性直接访问原始数据
if(key === 'raw'){
return target
}
//如果数据不是只读的,就收集相关依赖
if(!isReadonly){
track(target, "get", key);
}
//通过Reflect方法获取代理对象的属性值
const res = Reflect.get(target, key, receiver);
//如果数据是浅层响应的,直接返回获取的值
if(isShallow){
return res
}
//如果获取的值是对象,就递归调用reactive函数将其转化为响应式数据
if(typeof res === 'object' && res !== null){
//判断是否只读
return isReadonly ? readonly(res) : reactive(res)
}
return res
},
// 拦截设置属性值的操作
set(target, key, value, receiver) {
//如果数据是只读的,就会产生警告,无法设置
if(isReadonly){
console.warn(`Set operation on key "${key}" failed: target is readonly.`);
return false
}
//获取旧值
const oldValue = target[key];
//判断操作类型,是新增属性还是修改属性值,支持数组
const type = Array.isArray(target) ?
//如果原始数据是数组,判断索引值是否小于数组长度
Number(key) < target.length ? 'set' : 'add' :
//如果原始数据不是数组,判断属性是否存在
Object.prototype.hasOwnProperty.call(target, key) ? 'set' : 'add';
//通过Reflect方法设置代理对象的属性值
const res = Reflect.set(target, key, value, receiver);
//如果设置的值与旧值不同,就触发相关依赖
if(oldValue !== value){
trigger(target, key,"set",value);
}
return res;
},
// 拦截删除属性值的操作
deleteProperty(target, key) {
const res = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
trigger(target, "set", key);
}
return res;
},
// 拦截遍历属性值的操作
ownKeys(target) {
track(target, "iterate", isReadonly ? undefined : "value");
return Reflect.ownKeys(target);
},
// 拦截判断属性值是否存在的操作
has(target, key) {
track(target, "has", key);
return Reflect.has(target, key);
},
})
}