响应式 = 同一份引用 + 依赖跟踪 + 变更通知
GET读取 SET修改 Proxy总控 响应式数据 Track收集依赖 Trigger通知更新 Dep管理依赖 Effect改变值 视图更新 Ref基本类型 Reactive引用类型 toRef/toRefs同步引用属性
同一份引用
既然要做到同份引用,就要把 非对象 也变成对象
ref = reactive({value:initVal})
js
function ref (initValue) {
return reactive({
value: initValue
})
}
let num = ref(5)//num就会成为一个响应式的数据
console.log(num.value) // 5
ref VS reactive
| 特性 | reactive 【推荐 引用类型,避免冗长.value】 | ref【推荐 基本数据类型】 |
|---|---|---|
| 数据类型 | ❌只支持引用数据类型 | ✅支持基本数据类型+引用数据类型 |
| 属性访问 | ✅在 script 和 template中直接访问 | ❌template中自动解包,可直接访问,script中要.value |
| 属性解构 | ❌解构时会丢失响应性,需使用toRef/toRefs | ❌解构时会丢失响应性,需使用toRef/toRefs |
ref、reactive VS shallowRef、shallowReactive【仅在根层次】
toRef/toRefs解构保持响应
响应式对象的属性 批量解构响应式对象 toRef 原数据引用 toRefs 多个原数据引用 同步变化
js
const state = reactive({
foo: 1,
bar: 2
})
const {foo, bar} = toRefs(state)
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo,foo) // 2
收集依赖 + 变更通知
手动
ref+effec实现computed
effect():更改数据后执行,更新依赖该数据的数据(依赖)
js
function computed(fn) {
const result = ref()
effect(() => result.value = fn()) // 执行computed传入函数
return result
}
let num1 = ref(5)
let num2 = ref(8)
let sum1 = computed(() => num1.value * num2.value)
let sum2 = computed(() => sum1.value * 10)
//初始值
console.log(sum1.value) // 40
console.log(sum2.value) // 400
//响应式顺序:
//num1->改变了effect中依赖num1->执行effect传入的函数
//->改变了响应式的result->sum1在初始化时已经被赋予了响应式的result,所以sum1改变
num1.value = 10
console.log(sum1.value) // 80
console.log(sum2.value) // 800
num2.value = 16
console.log(sum1.value) // 160
console.log(sum2.value) // 1600
effect():类似vue2中的watch
js
let name = '林三心', age = 22, money = 20
let myself = '', ohtherMyself = ''
const effect1 = () => myself = `${name}今年${age}岁,存款${money}元`
const effect2 = () => ohtherMyself = `${age}岁的${name}居然有${money}元`
effect1() // 先执行一次
effect2() // 先执行一次
console.log(myself) // 林三心今年22岁,存款20元
console.log(ohtherMyself) // 22岁的林三心居然有20元
money = 300
effect1() // 再执行一次
effect2() // 再执行一次
console.log(myself) // 林三心今年22岁,存款300元
console.log(ohtherMyself) // 22岁的林三心居然有300元
track收集依赖的effect放进dep(set去重)
更新时触发trigger函数通知dep里所有effect执行
js
let name = '林三心', age = 22, money = 20
let myself = '', ohtherMyself = ''
const effect1 = () => myself = `${name}今年${age}岁,存款${money}元`
const effect2 = () => ohtherMyself = `${age}岁的${name}居然有${money}元`
const dep = new Set()
function track () {
dep.add(effect1)
dep.add(effect2)
}
function trigger() {
dep.forEach(effect => effect())
}
track() //收集依赖
effect1() // 先执行一次
effect2() // 先执行一次
console.log(myself) // 林三心今年22岁,存款20元
console.log(ohtherMyself) // 22岁的林三心居然有20元
money = 300
trigger() // 通知变量myself和otherMyself进行更新
console.log(myself) // 林三心今年22岁,存款300元
console.log(ohtherMyself) // 22岁的林三心居然有300元
当依赖的为object类型:WeakMap存obj,Map存obj.props

js
const targetMap = new WeakMap()
function track(target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, depsMap = new Map())
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, dep = new Set())
}
...
}
function trigger(target, key) {
let depsMap = targetMap.get(target)
if (depsMap) {
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
}
proxy
new Proxy(target, handler)
包装target (对象/数组/函数甚/proxy对象)
被代理对象上的自定义行为(定义一组处理函数(例如get、set)的对象)
target:被代理者
prop:被代理者的属性
receiver:代理者 ,Proxy 或者继承 Proxy 的对象
js
const person = { name: '林三心', age: 22 }
const proxyPerson = new Proxy(person, {
get(target, key, receiver) {
return target[key]
},
set(target, key, value, receiver) {
target[key] = value
}
})
console.log(proxyPerson.name) // 林三心
proxyPerson.name = 'sunshine_lin'
console.log(proxyPerson.name) // sunshine_lin
Reflect
Proxy和Reflect的方法都是一一对应的,在Proxy里使用Reflect会提高语义化
Proxy的get对应Reflect.getProxy的set对应Reflect.set
属性访问方法
A.属性访问器(访问):obj.key,obj[key]
B.函数调用:mp.get(key)
Reflect.get(target, propertyKey[, receiver])函数调用方式从对象中读值
如果
target对象中指定了getter,receiver则为getter调用时的this值该方法会拦截目标对象的以下操作:
js
const person = { name: '林三心', age: 22 }
const proxyPerson = new Proxy(person, {
get(target, key, receiver) {
return Reflect.get(receiver, key) // 相当于 receiver[key]
},
set(target, key, value, receiver) {
Reflect.set(receiver, key, value) // 相当于 receiver[key] = value
}
})
console.log(proxyPerson.name)
proxyPerson.name = 'sunshine_lin'
// 会直接报错,栈内存溢出 Maximum call stack size exceeded

reactive(被依赖的数据){return proxy}
js
function reactive(target) {
const handler = {
get(target, key, receiver) {
track(receiver, key) // 访问时收集依赖
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver)
trigger(receiver, key) // 设值时自动通知更新
}
}
return new Proxy(target, handler)
}
const person = reactive({ name: '林三心', age: 22 }) // 传入reactive
const animal = reactive({ type: 'dog', height: 50 }) // 传入reactive
vue3新增全局变量activeEffect存储当前执行的effect
track不必再判断obj和obj.key,直接dep存储当前activeEffect
js
let activeEffect = null
function effect(fn) {
activeEffect = fn
activeEffect()
activeEffect = null // 执行后立马变成null
}
function track(target, key) {
// 如果此时activeEffect为null则不执行下面
// 这里判断是为了避免例如console.log(person.name)而触发track
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, depsMap = new Map())
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, dep = new Set())
}
dep.add(activeEffect) // 把此时的activeEffect添加进去
}
// 每个effect函数改成这么执行
effect(effectNameStr1)
effect(effectNameStr2)
effect(effectAgeStr1)
effect(effectAgeStr2)
effect(effectTypeStr1)
effect(effectTypeStr2)
effect(effectHeightStr1)
effect(effectHeightStr2)