这篇文章主要讲解了“vue3中reactive和ref的区别是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue3中reactive和ref的区别是什么”吧!
成都创新互联公司成立于2013年,先为浑源等服务建站,浑源等地企业,进行企业商务咨询服务。为浑源企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。
reactive
源码地址:packages/reactivity/reactive.ts
首先我们看一下vue3中用来标记目标对象target类型的ReactiveFlags
// 标记目标对象 target 类型的 ReactiveFlags
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
export interface Target {
[ReactiveFlags.SKIP]?: boolean // 不做响应式处理的数据
[ReactiveFlags.IS_REACTIVE]?: boolean // target 是否是响应式
[ReactiveFlags.IS_READONLY]?: boolean // target 是否是只读
[ReactiveFlags.RAW]?: any // 表示proxy 对应的源数据, target 已经是 proxy 对象时会有该属性
}reactive
export function reactive(target: T): UnwrapNestedRefs export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. // 如果目标对象是一个只读的响应数据,则直接返回目标对象 if (target && (target as Target)[ReactiveFlags.IS_READONLY]) { return target } // 创建 observe return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap ) }
reactive函数接收一个target对象,如果target对象只读则直接返回该对象
若非只读则直接通过createReactiveObject创建observe对象
createReactiveObject
看着长不要怕,先贴createReactiveObject完整代码,我们分段阅读
/** * * @param target 目标对象 * @param isReadonly 是否只读 * @param baseHandlers 基本类型的 handlers * @param collectionHandlers 主要针对(set、map、weakSet、weakMap)的 handlers * @param proxyMap WeakMap数据结构 * @returns */ function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler, collectionHandlers: ProxyHandler , proxyMap: WeakMap ) { // typeof 不是 object 类型的,在开发模式抛出警告,生产环境直接返回目标对象 if (!isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) } return target } // target is already a Proxy, return it. // exception: calling readonly() on a reactive object // 已经是响应式的就直接返回(取ReactiveFlags.RAW 属性会返回true,因为进行reactive的过程中会用weakMap进行保存, // 通过target能判断出是否有ReactiveFlags.RAW属性) // 例外:对reactive对象进行readonly() if ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) { return target } // target already has corresponding Proxy // 对已经Proxy的,则直接从WeakMap数据结构中取出这个Proxy对象 const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy } // only a whitelist of value types can be observed. // 只对targetTypeMap类型白名单中的类型进行响应式处理 const targetType = getTargetType(target) if (targetType === TargetType.INVALID) { return target } // proxy 代理 target // (set、map、weakSet、weakMap) collectionHandlers // (Object、Array) baseHandlers const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) proxyMap.set(target, proxy) return proxy }
首先我们看到createReactiveObject接收了五个参数
target: Target, isReadonly: boolean, baseHandlers: ProxyHandler, collectionHandlers: ProxyHandler , proxyMap: WeakMap
target 目标对象
isReadonly 是否只读
baseHandlers 基本类型的 handlers处理数组,对象
collectionHandlers处理 set、map、weakSet、weakMap
proxyMapWeakMap数据结构存储副作用函数
这里主要是通过ReactiveFlags.RAW和ReactiveFlags.IS_REACTIVE判断是否是响应式数据,若是则直接返回该对象
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}对于已经是Proxy的,则直接从WeakMap数据结构中取出这个Proxy对象并返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}这里则是校验了一下当前target的类型是不是Object、Array、Map、Set、WeakMap、WeakSet,如果都不是则直接返回该对象,不做响应式处理
// 只对targetTypeMap类型白名单中的类型进行响应式处理
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}校验类型的逻辑
function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}所有的前置校验完后,就可以使用proxy 代理target对象了
这里使用了一个三目运算符通过TargetType.COLLECTION来执行不同的处理逻辑
(set、map、weakSet、weakMap) 使用
collectionHandlers(Object、Array) 使用
baseHandlers
// proxy 代理 target // (set、map、weakSet、weakMap) collectionHandlers // (Object、Array) baseHandlers const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) proxyMap.set(target, proxy) return proxy
现在对createReactiveObject的执行逻辑是不是就很清晰了
到这里还没有结束,createReactiveObject中最后proxy是如何去代理target的呢?这里我们用baseHandlers举例,深入baseHandlers的内部去看看
baseHandlers
源码地址:packages/reactivity/baseHandlers.ts
在reactive.ts中我们可以看到一共引入了四种 handler
import {
mutableHandlers,
readonlyHandlers,
shallowReactiveHandlers,
shallowReadonlyHandlers
} from './baseHandlers'mutableHandlers可变处理readonlyHandlers只读处理shallowReactiveHandlers浅观察处理(只观察目标对象的第一层属性)shallowReadonlyHandlers浅观察 && 只读
我们以mutableHandlers为例
// 可变处理 // const get = /*#__PURE__*/ createGetter() // const set = /*#__PURE__*/ createSetter() // get、has、ownKeys 会触发依赖收集 track() // set、deleteProperty 会触发更新 trigger() export const mutableHandlers: ProxyHandler
这里的get和set分别对应着createGetter()、createSetter()
createGetter()
先上完整版代码
/**
* 用于拦截对象的读取属性操作
* @param isReadonly 是否只读
* @param shallow 是否浅观察
* @returns
*/
function createGetter(isReadonly = false, shallow = false) {
/**
* @param target 目标对象
* @param key 需要获取的值的键值
* @param receiver 如果遇到 setter,receiver 则为setter调用时的this值
*/
return function get(target: Target, key: string | symbol, receiver: object) {
// ReactiveFlags 是在reactive中声明的枚举值,如果key是枚举值则直接返回对应的布尔值
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (
// 如果key是raw receiver 指向调用者,则直接返回目标对象。
// 这里判断是为了保证触发拦截 handle 的是 proxy 本身而不是 proxy 的继承者
// 触发拦的两种方式:一是访问 proxy 对象本身的属性,二是访问对象原型链上有 proxy 对象的对象的属性,因为查询会沿着原型链向下找
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
const targetIsArray = isArray(target)
// 如果目标对象 不为只读、是数组、key属于arrayInstrumentations:['includes', 'indexOf', 'lastIndexOf']方法之一,即触发了这三个方法之一
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
// 通过 proxy 调用,arrayInstrumentations[key]的this一定指向 proxy
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
// 如果 key 是 symbol 内置方法,或者访问的是原型对象__proto__,直接返回结果,不收集依赖
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 不是只读类型的 target 就收集依赖。因为只读类型不会变化,无法触发 setter,也就会触发更新
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果是浅观察,不做递归转化,就是说对象有属性值还是对象的话不递归调用 reactive()
if (shallow) {
return res
}
// 如果get的结果是ref
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
// 返回 ref.value,数组除外
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 由于 proxy 只能代理一层,如果子元素是对象,需要递归继续代理
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}看着长,最终就是track()依赖收集
createSetter()
/**
* 拦截对象的设置属性操作
* @param shallow 是否是浅观察
* @returns
*/
function createSetter(shallow = false) {
/**
* @param target 目标对象
* @param key 设置的属性名称
* @param value 要改变的属性值
* @param receiver 如果遇到setter,receiver则为setter调用时的this值
*/
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
// 如果模式不是浅观察模式
if (!shallow) {
// 拿新值和老值的原始值,因为新传入的值可能是响应式数据,如果直接和 target 上原始值比较是没有意义的
value = toRaw(value)
oldValue = toRaw(oldValue)
// 目标对象不是数组,旧值是ref,新值不是ref,则直接赋值,这里提到ref
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
// 检查对象是否有这个属性
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// 赋值
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
// reactive是proxy实例才触发更新,防止通过原型链触发拦截器触发更新
if (target === toRaw(receiver)) {
if (!hadKey) {
// 如果不存在则trigger ADD
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 如果新旧值不相等则trigger SET
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}trigger()触发更新
ref
源码地址:packages/reactivity/src/ref.ts
接收一个可选unknown,接着直接调用createRef()
export function ref(value?: unknown) {
return createRef(value, false)
}
与ref的区别就是在调用createRef()时第二个值传的是true
export function shallowRef(value?: unknown) {
return createRef(value, true)
}看一下官方文档上对shallowRef的解释

createRef
通过isRef()判断是否是ref数据,是则直接返回该数据,不是则通过new RefImpl创建ref数据
在创建时会传两个值一个是rawValue(原始值),一个是shallow(是否是浅观察),具体使用场景可看上面ref和shallowRef的介绍
function createRef(rawValue: unknown, shallow: boolean) {
// 是否是 ref 数据
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}isRef()
通过__v_isRef只读属性判断是否是ref数据,此属性会在RefImpl创建ref数据时添加
export function isRef(r: any): r is Ref {
return Boolean(r && r.__v_isRef === true)
}RefImpl
class RefImpl{ private _value: T private _rawValue: T public dep?: Dep = undefined // 只读属性 __v_isRef 判断是否是ref数据的静态标识 public readonly __v_isRef = true constructor(value: T, public readonly _shallow: boolean) { this._rawValue = _shallow ? value : toRaw(value) // 非浅观察用toRaw()包裹原始值 this._value = _shallow ? value : toReactive(value) // 非浅观察用toReactive()处理数据 } get value() { // 依赖收集 trackRefValue(this) return this._value } set value(newVal) { newVal = this._shallow ? newVal : toRaw(newVal) // 非浅观察用toRaw()包裹值 // 两个值不相等 if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : toReactive(newVal) triggerRefValue(this, newVal) // 触发依赖,派发更新 } } }
根据RefImpl我们可以看到ref的底层逻辑,如果是对象确实会使用reactive进行处理,并且ref的创建使用的也是RefImpl class实例,value只是RefImpl的属性
在我们访问和设置 ref的value值时,也分别是通过get和set拦截进行依赖收集和派发更新的
toReactive
我们来看一下toReactive()这个方法,在RefImpl中创建ref数据时会调用toReactive()方法,这里会先判断传进来的值是不是对象,如果是就用reactive()包裹,否则就返回其本身
export const toReactive =(value: T): T => isObject(value) ? reactive(value) : value
trackRefValue
ref的依赖收集方法
export function trackRefValue(ref: RefBase) { if (isTracking()) { ref = toRaw(ref) if (!ref.dep) { ref.dep = createDep() } if (__DEV__) { trackEffects(ref.dep, { target: ref, type: TrackOpTypes.GET, key: 'value' }) } else { trackEffects(ref.dep) } } }
triggerRefValue
ref的派发更新方法
export function triggerRefValue(ref: RefBase, newVal?: any) { ref = toRaw(ref) if (ref.dep) { if (__DEV__) { triggerEffects(ref.dep, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { triggerEffects(ref.dep) } } }
总结
看完reactive和ref源码,相信对本文一开始的几个问题也都有了答案,这里也总结了几个问题:
问:ref的底层逻辑是什么,具体是如何实现的
答:ref底层会通过 new RefImpl()来创造ref数据,在new RefImpl()会首先给数据添加__v_isRef只读属性用来标识ref数据。而后判断传入的值是否是对象,如果是对象则使用toReactive()处理成reactive,并将值赋给RefImpl()的value属性上。在访问和设置ref数据的value时会分别触发依赖收集和派发更新流程。
问:ref底层是否会使用
reactive处理数据
答:RefImpl中非浅观察会调用toReactive()方法处理数据,toReactive()中会先判断传入的值是不是一个对象,如果是对象则使用reactive进行处理,不是则直接返回值本身。
问:为什么已经有了
reactive还需要在设计一个ref呢?
答: 因为vue3响应式方案使用的是proxy,而proxy的代理目标必须是非原始值,没有任何方式能去拦截对原始值的操作,所以就需要一层对象作为包裹,间接实现原始值的响应式方案。
问:为什么
ref数据必须要有个value属性,访问ref数据必须要通过.value的方式呢?
答:这是因为要解决响应式丢失的问题,举个例子:
// obj是响应式数据
const obj = reactive({ foo: 1, bar: 2 })
// newObj 对象下具有与 obj对象同名的属性,并且每个属性值都是一个对象
// 该对象具有一个访问器属性 value,当读取 value的值时,其实读取的是 obj 对象下相应的属性值
const newObj = {
foo: {
get value() {
return obj.foo
}
},
bar: {
get value() {
return obj.bar
}
}
}
effect(() => {
// 在副作用函数内通过新对象 newObj 读取 foo 的属性值
console.log(newObj.foo)
})
// 正常触发响应
obj.foo = 100可以看到,在现在的newObj对象下,具有与obj对象同名的属性,而且每个属性的值都是一个对象,例如foo 属性的值是:
{
get value() {
return obj.foo
}
}该对象有一个访问器属性value,当读取value的值时,最终读取的是响应式数据obj下的同名属性值。也就是说,当在副作用函数内读取newObj.foo时,等价于间接读取了obj.foo的值。这样响应式数据就能够与副作用函数建立响应联系
感谢各位的阅读,以上就是“vue3中reactive和ref的区别是什么”的内容了,经过本文的学习后,相信大家对vue3中reactive和ref的区别是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!
名称栏目:vue3中reactive和ref的区别是什么
分享地址:http://www.jxjierui.cn/article/jdigcc.html


咨询
建站咨询
