做网站公司上海,做网站除了域名还用什么,怎么样才能自己建网站,深圳正规网站建设ref 和 reactive 是 Vue3 中实现响应式数据的核心 API。ref 用于包装基本数据类型#xff0c;而 reactive 用于处理对象和数组。尽管 reactive 似乎更适合处理对象#xff0c;但 Vue3 官方文档更推荐使用 ref。 我的想法#xff0c;ref就是比reactive好用#xff0c;官方也…ref 和 reactive 是 Vue3 中实现响应式数据的核心 API。ref 用于包装基本数据类型而 reactive 用于处理对象和数组。尽管 reactive 似乎更适合处理对象但 Vue3 官方文档更推荐使用 ref。 我的想法ref就是比reactive好用官方也是这么说的不服来踩下面我们从源码的角度详细讨论这两个 API以及 Vue3 为什么推荐使用ref而不是reactive ref 的内部工作原理
ref 是一个函数它接受一个内部值并返回一个响应式且可变的引用对象。这个引用对象有一个 .value 属性该属性指向内部值。
// 深响应式
export function ref(value?: unknown) {return createRef(value, false)
}// 浅响应式
export function shallowRef(value?: unknown) {return createRef(value, true)
}function createRef(rawValue: unknown, shallow: boolean) {// 如果传入的值已经是一个 ref则直接返回它if (isRef(rawValue)) {return rawValue}// 否则创建一个新的 RefImpl 实例return new RefImpl(rawValue, shallow)
}class RefImplT {// 存储响应式的值。我们追踪和更新的就是_value。这个是重点private _value: T// 用于存储原始值即未经任何响应式处理的值。用于对比的这块的内容可以不看private _rawValue: T // 用于依赖跟踪的 Dep 类实例public dep?: Dep undefined// 一个标记表示这是一个 ref 实例public readonly __v_isRef trueconstructor(value: T,public readonly __v_isShallow: boolean,) {// 如果是浅响应式直接使用原始值否则转换为非响应式原始值this._rawValue __v_isShallow ? value : toRaw(value)// 如果是浅响应式直接使用原始值否则转换为响应式值this._value __v_isShallow ? value : toReactive(value)// toRaw 用于将响应式引用转换回原始值// toReactive 函数用于将传入的值转换为响应式对象。对于基本数据类型toReactive 直接返回原始值。// 对于对象和数组toReactive 内部会调用 reactive 来创建一个响应式代理。// 因此对于 ref 来说基本数据类型的值会被 RefImpl 直接包装而对象和数组// 会被 reactive 转换为响应式代理最后也会被 RefImpl 包装。// 这样无论是哪种类型的数据ref 都可以提供响应式的 value 属性// 使得数据变化可以被 Vue 正确追踪和更新。// export const toReactive (value) isObject(value) ? reactive(value) : value}get value() {// 追踪依赖这样当 ref 的值发生变化时依赖这个 ref 的组件或副作用函数可以重新运行。trackRefValue(this)// 返回存储的响应式值return this._value}set value(newVal) {// 判断是否应该使用新值的直接形式浅响应式或只读const useDirectValue this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)// 如果需要将新值转换为非响应式原始值newVal useDirectValue ? newVal : toRaw(newVal)// 如果新值与旧值不同更新 _rawValue 和 _valueif (hasChanged(newVal, this._rawValue)) {this._rawValue newValthis._value useDirectValue ? newVal : toReactive(newVal)// 触发依赖更新triggerRefValue(this, DirtyLevels.Dirty, newVal)}}
}在上述代码中ref 函数通过 new RefImpl(value) 创建了一个新的 RefImpl 实例。这个实例包含 getter 和 setter分别用于追踪依赖和触发更新。使用 ref 可以声明任何数据类型的响应式状态包括对象和数组。
import { ref } from vue let state ref({ count: 0 })
state.value.count注意ref核心是返回响应式且可变的引用对象而reactive核心是返回的是响应式代理这是两者本质上的核心区别也就导致了ref优于reactive我们接着看下reactive源码实现。
reactive 的内部工作原理
reactive 是一个函数它接受一个对象并返回该对象的响应式代理也就是 Proxy。
function reactive(target) {if (target target.__v_isReactive) {return target}return createReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers,reactiveMap)
}function createReactiveObject(target,isReadonly,baseHandlers,collectionHandlers,proxyMap
) {if (!isObject(target)) {return target}const existingProxy proxyMap.get(target)if (existingProxy) {return existingProxy}const proxy new Proxy(target, baseHandlers)proxyMap.set(target, proxy)return proxy
}reactive的源码相对就简单多了reactive 通过 new Proxy(target, baseHandlers) 创建了一个代理。这个代理会拦截对目标对象的操作从而实现响应式。
import { reactive } from vue let state reactive({ count: 0 })
state.count到这里我们可以看出 ref 和 reactive 在声明数据的响应式状态上底层原理是不一样的。ref 采用 RefImpl对象实例reactive采用Proxy代理对象。
ref 更深入的理解
当你使用 new RefImpl(value) 创建一个 RefImpl 实例时这个实例大致上会包含以下几部分
内部值实例存储了传递给构造函数的初始值。依赖收集实例需要跟踪所有依赖于它的效果effect例如计算属性或者副作用函数。这通常通过一个依赖列表或者集合来实现。触发更新当实例的值发生变化时它需要通知所有依赖于它的效果以便它们可以重新计算或执行。
RefImpl 类似于发布-订阅模式的设计以下是一个简化的 RefImpl 类的伪代码实现展示这个实现过程
class Dep {constructor() {this.subscribers new Set();}depend() {if (activeEffect) {this.subscribers.add(activeEffect);}}notify() {this.subscribers.forEach(effect effect());}
}let activeEffect null;function watchEffect(effect) {activeEffect effect;effect();activeEffect null;
}class RefImpl {constructor(value) {this._value value;this.dep new Dep();}get value() {// 当获取值时进行依赖收集this.dep.depend();return this._value;}set value(newValue) {if (newValue ! this._value) {this._value newValue;// 值改变时触发更新this.dep.notify();}}
}// 使用示例
let count new RefImpl(0);watchEffect(() {console.log(The count is: ${count.value}); // 订阅变化
});count.value; // 修改值触发通知重新执行watchEffect中的函数Dep 类负责管理一个依赖列表并提供依赖收集和通知更新的功能。RefImpl 类包含一个内部值 _value 和一个 Dep 实例。当 value 被访问时通过 get 方法进行依赖收集当 value 被赋予新值时通过 set 方法触发更新。 ref 和 reactive 尽管两者在内部实现上有所不同但它们都能满足我们对于声明响应式变量的要求但是 reactive 却存在一定的局限性。
reactive 的局限性
在 Vue3 中reactive API 通过 Proxy 实现了一种响应式数据的方法尽管这种方法在性能上比 Vue2 有所提升但 Proxy 的局限性也导致了 reactive 的局限性这些局限性可能会影响开发者的使用体验。
仅对引用数据类型有效
reactive 主要适用于对象包括数组和一些集合类型如 Map 和 Set。对于基础数据类型如 string、number 和 booleanreactive 是无效的。这意味着如果你尝试使用 reactive 来处理这些基础数据类型将会得到一个非响应式的对象。
import { reactive } from vue;
const state reactive({ count: 0 });使用不当会失去响应 直接赋值对象如果直接将一个响应式对象赋值给另一个变量将会失去响应性。这是因为 reactive 返回的是对象本身而不仅仅是代理。 import { reactive } from vue;let state reactive({ count: 0 });
state { count: 1 }; // 失去响应性直接替换响应式对象同样直接替换一个响应式对象也会导致失去响应性。 import { reactive } from vue;let state reactive({ count: 0 });
state reactive({ count: 1 }); // 失去响应性直接解构对象在解构响应式对象时如果直接解构对象属性将会得到一个非响应式的变量。 const state reactive({ count: 0 });let { count } state;
count; // count 仍然是 0解决这个问题需要使用 toRefs 函数来将响应式对象转换为 ref 对象。 import { toRefs } from vue;const state reactive({ count: 0 });
let { count } toRefs(state);
count; // count 现在是 1将响应式对象的属性赋值给变量如果将响应式对象的属性赋值给一个变量这个变量的值将不会是响应式的。 let state reactive({ count: 0 })let count state.count
count // count 仍然是 0
console.log(state.count)使用 reactive 声明响应式变量的确存在一些不便之处尤其是对于喜欢使用解构赋值的开发者而言。这些局限性可能会导致意外的行为因此在使用 reactive 时需要格外注意。相比之下ref API 提供了一种更灵活和统一的方式来处理响应式数据。
为什么推荐使用 ref
ref()它为响应式编程提供了一种统一的解决方案适用于所有类型的数据包括基本数据类型和复杂对象。以下是推荐使用 ref 的几个关键原因
统一性
ref 的核心优势之一是它的统一性。它提供了一种简单、一致的方式来处理所有类型的数据无论是数字、字符串、对象还是数组。这种统一性极大地简化了开发者的代码减少了在不同数据类型之间切换时的复杂性。
import { ref } from vue;let num ref(0);
let str ref(Hello);
let obj ref({ count: 0 });// 修改基本数据类型
num.value;
str.value World;// 修改对象
obj.value.count;深层响应性
ref 支持深层响应性这意味着它可以追踪和更新嵌套对象和数组中的变化。这种特性使得 ref 非常适合处理复杂的数据结构如对象和数组。
import { ref } from vue;let obj ref({user: {name: xiaoming,details: {age: 18}}
});// 修改嵌套对象
obj.value.user.details.age;当然为了减少大型不可变数据的响应式开销也可以通过使用shallowRef来放弃深层响应性。
let shallowObj shallowRef({ details: { age: 18, },
});灵活性
ref 提供了高度的灵活性尤其在处理普通赋值方面。这种灵活性使得 ref 在开发中的使用更加方便特别是在进行复杂的数据操作时。
import { ref } from vue;let state ref({count: 0,name: Vue
});// 替换整个对象
state.value {count: 10,name: Vue 4
};
// 修改对象内的属性
state.value.count 20;
state.value.name Vue 5;
// 添加新的属性
state.value.newProperty New Property;
// 删除属性
delete state.value.newProperty;
// 使用解构更新属性注意要保持响应性
let { count, name } state.value;
state.value { count: count 1, name };
// 复杂操作例如根据条件更新属性
if (someCondition) {state.value {...state.value,name: Updated Name};
}
console.log(state.value)总结
ref 在 Vue3 中提供了一种更统一、灵活的响应式解决方案还能避免了 reactive 的某些局限性。希望这篇文章对你有所帮助有所借鉴。大家怎么认为呢评论区我们一起讨论下