vue3源码:reactive与ref深度对比,为什么要使用 ref
reactive的局限性:reactive
函数会把传入的object作为proxy
的target
参数,而proxy
只能代理对象,不能代理基本数据类型
ref的实现原理:在类RefImpl
的构造函数中执行了一个toReactive
方法,传入了value
,该方法把数据分成了两种类型:
- 复杂数据类型:调用了
reactive
函数,即把value
变为响应性的。 - 简单数据类型:直接把
value
原样返回。
reactive简要源码
- 触发
reactive
方法:return createReactiveObject
- 执行
createReactiveObject
方法:从WeakMap
缓存对象中读取object- 存在:
return existingProxy
- 不存在:生成
proxy
,并执行proxyMap.set(target, proxy)
进行缓存,最后return proxy
- 存在:
- 最终都是返回了代理对象。
import { mutableHandlers } from './baseHandlers'
/**
* 响应性 Map 缓存对象
* key:target
* val:proxy
*/
export const reactiveMap = new WeakMap<object, any>()
/**
* 为复杂数据类型,创建响应性对象
* @param target 被代理对象
* @returns 代理对象
*/
export function reactive(target: object) {
return createReactiveObject(target, mutableHandlers, reactiveMap)
}
/**
* 创建响应性对象
* @param target 被代理对象
* @param baseHandlers handler
*/
function createReactiveObject(
target: object,
baseHandlers: ProxyHandler<any>,
proxyMap: WeakMap<object, any>
) {
// 如果该实例已经被代理,则直接读取即可
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 未被代理,则生成 proxy 实例
const proxy = new Proxy(target, baseHandlers)
// 缓存代理对象
proxyMap.set(target, proxy)
return proxy
}
baseHandlers 就是导入的 mutableHandlers
/**
*
响应性的 handler
*/
export const mutableHandlers: ProxyHandler<object> = {
get,
set
}
/**
* getter 回调方法
*/
const get = createGetter()
/**
* 创建 getter 回调方法
*/
function createGetter() {
return function get(target: object, key: string | symbol, receiver: object) {
// 利用 Reflect 得到返回值
const res = Reflect.get(target, key, receiver)
// todo收集依赖
// track(target, key)
return res
}
}
/**
* setter 回调方法
*/
const set = createSetter()
/**
* 创建 setter 回调方法
*/
function createSetter() {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
) {
// 利用 Reflect.set 设置新值
const result = Reflect.set(target, key, value, receiver)
// todo触发依赖
// trigger(target, key, value)
return result
}
}
ref简要源码
ref
函数中,直接触发createRef
函数- 在
createRef
中,进行了判断如果当前已经是一个ref
类型数据则直接返回,否则 返回**RefImpl**
类型的实例 RefImpl
类的作用- 该类的构造函数中,执行了一个
toReactive
的方法,传入了value
并把返回值赋值给了this._value
,来看看toReactive
的作用:toReactive
方法把数据分成了两种类型:- 复杂数据类型:调用了
reactive
函数,即把value
变为响应性的。 - 简单数据类型:直接把
value
原样返回
- 复杂数据类型:调用了
- 该类提供了一个分别被
get
和set
标记的函数value
- 当执行
xxx.value
时,会触发get
标记 - 当执行
xxx.value = xxx
时,会触发set
标记
- 当执行
- 该类的构造函数中,执行了一个
- 至此
ref
函数执行完成。
1、复杂数据类型
import { createDep, Dep } from './dep'
import { activeEffect, trackEffects } from './effect'
import { toReactive } from './reactive'
export interface Ref<T = any> {
value: T
}
/**
* ref 函数
* @param value unknown
*/
export function ref(value?: unknown) {
return createRef(value, false)
}
/**
* 创建 RefImpl 实例
* @param rawValue 原始数据
* @param shallow boolean 形数据,表示《浅层的响应性(即:只有 .value 是响应性的)》
* @returns
*/
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
class RefImpl<T> {
private _value: T
public dep?: Dep = undefined
// 是否为 ref 类型数据的标记
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._value = __v_isShallow ? value : toReactive(value)
}
/**
* get语法将对象属性绑定到查询该属性时将被调用的函数。
* 即:xxx.value 时触发该函数
* get value(){} 是使用“getter”方法的写法,用于定义对象的属性访问器。Getter 方法定义的方式是使用get关键字后跟一个函数,这个函数的名字就是属性名,无需调用这个函数,只需像访问属性一样使用该属性即可。
*/
get value() {
// trackRefValue(this)
return this._value
}
set value(newVal) {}
}
/**
* 指定数据是否为 RefImpl 类型
*/
export function isRef(r: any): r is Ref {
return !!(r && r.__v_isRef === true)
}
/**
* 将指定数据变为 reactive 数据
*/
export const toReactive = <T extends unknown>(value: T): T => isObject(value) ? reactive(value as object) : value
/**
* 判断是否为一个对象
*/
export const isObject = (val: unknown) => val !== null && typeof val === 'object'
总结:本质上实现方案是完全相同的,都是利用 reactive
函数,返回一个 proxy
实例,监听 proxy
的 getter
和 setter
进行的依赖收集和依赖触发。但是中间有些不同之处:
ref
:ref
的返回值是一个RefImpl
类型的实例对象- 想要访问
ref
的真实数据,需要通过.value
来触发get value
函数,得到被toReactive
标记之后的this._value
数据,即:proxy
实例
reactive
:
2、简单数据类型
class RefImpl<T> {
private _value: T
private _rawValue: T
...
constructor(value: T, public readonly __v_isShallow: boolean) {
...
// 原始数据
this._rawValue = value
}
...
set value(newVal) {
/**
* newVal 为新数据
* this._rawValue 为旧数据(原始数据)
* 对比两个数据是否发生了变化
*/
if (hasChanged(newVal, this._rawValue)) {
// 更新原始数据
this._rawValue = newVal
// 更新 .value 的值
this._value = toReactive(newVal)
// 触发依赖
// triggerRefValue(this)
}
}
}
/**
* 对比两个数据是否发生了改变
*/
const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue)
总结:基本数据类型在 toReactive
函数中,不会通过 reactive
函数处理 value
。所以 this._value
不是 一个 proxy
。即:无法监听 setter
和 getter
。简单数据类型的响应性,不是基于 proxy
或 Object.defineProperty
进行实现的,而是通过 set
语法,将对象属性绑定到查询该属性时将被调用的函数 上,使其触发 xxx.value = '李四'
属性时,其实是调用了 xxx.value('李四')
函数。在 value
函数中,触发依赖。
注:在复杂数据类型下,(
obj.value.name = '李四'
),其实是触发了get value
行为,实际是分解为obj.value => proxy
,proxy.name
。但是,在 简单数据类型之下,obj.value = '李四'
触发的将是set value
形式,这里也是ref
** 可以监听到简单数据类型响应性的关键。
转载自:https://juejin.cn/post/7366567675315077129