likes
comments
collection
share

vue3源码:reactive与ref深度对比,为什么要使用 ref

作者站长头像
站长
· 阅读数 32

reactive的局限性reactive函数会把传入的object作为proxytarget参数,而proxy只能代理对象,不能代理基本数据类型

ref的实现原理:在类RefImpl的构造函数中执行了一个toReactive 方法,传入了value,该方法把数据分成了两种类型:

  1. 复杂数据类型:调用了 reactive 函数,即把 value 变为响应性的。
  2. 简单数据类型:直接把 value 原样返回。

下面进行核心源码解读

reactive简要源码

  1. 触发 reactive 方法:return createReactiveObject
  2. 执行createReactiveObject方法:从WeakMap缓存对象中读取object
    • 存在:return existingProxy
    • 不存在:生成proxy,并执行 proxyMap.set(target, proxy) 进行缓存,最后return proxy
  3. 最终都是返回了代理对象。
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简要源码

  1. ref 函数中,直接触发 createRef 函数
  2. createRef 中,进行了判断如果当前已经是一个 ref 类型数据则直接返回,否则 返回 **RefImpl** 类型的实例
  3. RefImpl 类的作用
    1. 该类的构造函数中,执行了一个 toReactive 的方法,传入了 value 并把返回值赋值给了 this._value,来看看 toReactive 的作用:
      1. toReactive 方法把数据分成了两种类型:
        1. 复杂数据类型:调用了 reactive 函数,即把 value 变为响应性的。
        2. 简单数据类型:直接把 value 原样返回
    2. 该类提供了一个分别被 getset 标记的函数 value
      1. 当执行 xxx.value 时,会触发 get 标记
      2. 当执行 xxx.value = xxx 时,会触发 set 标记
  4. 至此 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 实例,监听 proxygettersetter 进行的依赖收集和依赖触发。但是中间有些不同之处:

  1. ref
    1. ref 的返回值是一个 RefImpl 类型的实例对象
    2. 想要访问 ref 的真实数据,需要通过 .value 来触发 get value 函数,得到被 toReactive 标记之后的 this._value 数据,即:proxy 实例
  2. reactive
    1. reactive 会直接返回一个 proxy 的实例对象,不需要通过 .value 属性得到

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。即:无法监听 settergetter。简单数据类型的响应性,不是基于 proxyObject.defineProperty 进行实现的,而是通过 set语法,将对象属性绑定到查询该属性时将被调用的函数 上,使其触发 xxx.value = '李四' 属性时,其实是调用了 xxx.value('李四') 函数。在 value 函数中,触发依赖。

注:在复杂数据类型下,(obj.value.name = '李四'),其实是触发了 get value 行为,实际是分解为obj.value => proxyproxy.name。但是,在 简单数据类型之下,obj.value = '李四' 触发的将是  set value 形式,这里也是 ref** 可以监听到简单数据类型响应性的关键。

转载自:https://juejin.cn/post/7366567675315077129
评论
请登录