在上一节中, 实现了 使用proxy 生成reactive,
proxy可以处理复杂数据 比如对象, 但是对简单数据 比如 数组number 无法处理, 就需要通过ref方式对简单数据进行处理
ref的本质 和 proxy是相同的, 但是 ref目标是 对简单数据 进行处理
实现目标
<div id="app"></div>
<script>
const { ref, effect } = Vue
const obj = ref({
name: '张三'
})
// 调用 effect 方法
effect(() => {
document.querySelector('#app').innerText = obj.value.name
})
setTimeout(() => {
obj.value.name = '李四'
}, 2000)
</script>
实现原理
除了 proxy object.defineProperty 还有什么函数 可以 实现 读 写的时候 获取 值呢
可以通过 class 的 set get 方式 对value 进行读取
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
//__age__ = 0
get age() {
console.log("get is called")
//return this.__age__
return this.__proto__.__age__
}
set age(value) {
console.log("set is called")
if (value < 0) {
//this.__age__ = value * -1
this.__proto__.__age__ = value * -1
} else {
//this.__age__ = value
this.__proto__.__age__ = value
}
}
}
Person.prototype.__age__ = 0
let p1 = new Person("tom", -18)
p1.age=19
console.log(p1.age, p1)
创建 packages/reactivity/src/ref.ts
import { createDep, Dep } from './dep'
import { activeEffect, trackEffects, triggerEffects } from './effect'
import { toReactive } from './reactive'
import { hasChanged } from '@vue/shared'
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
private _rawValue: T
public dep?: Dep = undefined
// 是否为 ref 类型数据的标记
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
// 如果 __v_isShallow 为 true,则 value 不会被转化为 reactive 数据,即如果当前 value 为复杂数据类型,则会失去响应性。对应官方文档 shallowRef :https://cn.vuejs.org/api/reactivity-advanced.html#shallowref
// 原始数据
this._rawValue = value
this._value = __v_isShallow ? value : toReactive(value)
}
/**
* get语法将对象属性绑定到查询该属性时将被调用的函数。
* 即:xxx.value 时触发该函数
*/
get value() {
trackRefValue(this)
return this._value
}
set value(newVal) {
/**
* newVal 为新数据
* this._rawValue 为旧数据(原始数据)
* 对比两个数据是否发生了变化
*/
if (hasChanged(newVal, this._rawValue)) {
// 更新原始数据
this._rawValue = newVal
// 更新 .value 的值
this._value = toReactive(newVal)
// 触发依赖
triggerRefValue(this)
}
}
}
/**
* 为 ref 的 value 进行依赖收集工作
*/
export function trackRefValue(ref) {
console.log(activeEffect, '执行依赖工作')
if (activeEffect) {
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
/**
* 为 ref 的 value 进行触发依赖工作
*/
export function triggerRefValue(ref) {
if (ref.dep) {
triggerEffects(ref.dep)
}
}
/**
* 指定数据是否为 RefImpl 类型
*/
export function isRef(r: any): r is Ref {
return !!(r && r.__v_isRef === true)
}
在packages/reactivity/src/index.ts
export { reactive } from './reactive';
export { effect } from './effect';
export { ref } from './ref';
在 packages/vue/src/index.ts 中
export { reactive, effect, ref } from '@vue/reactivity';
创建example/reactivity/ref.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="../../dist/vue.js"></script>
</head>
<body>
<div id="app">
<p id="p1"></p>
<p id="p2"></p>
</div>
</body>
<script>
const { ref, effect } = Vue
const obj = ref({
name: '张三'
})
console.log(obj, 66)
const obj2 = ref('张三')
console.log(obj2.value)
// 调用 effect 方法
// effect(() => {
// document.querySelector('#app').innerText = obj.value.name
// })
effect(() => {
document.querySelector('#p2').innerText = obj2.value
})
setTimeout(() => {
// obj.value.name = '李四'
obj2.value = "李思思"
}, 2000)
</script>
</html>
运行npm run build 可以看到 ref功能实现成功
代码地址
参考文章