# Vue.js 3.0响应式原理
- 使用 Proxy 对象实现属性监听
- 多层属性嵌套,只有在访问属性过程中处理下一级属性
- 默认监听动态添加的属性
- 默认监听属性的删除操作
- 默认监听数组索引和 length 属性
- 可以作为单独的模块使用
# 核心方法
- reactive/ref/toRefs/computed
- 三个底层方法,一般不会直接调用
- effect
- track
- trigger
effect 函数用于定义副作用,它的参数就是副作用函数,会默认执行一次,当响应数据变化后,会导致副作用函数重新执行。
track 用来收集依赖(收集effect),trigger 用来触发响应(执行effect)
# reactive
- 接收一个参数,判断参数是否是对象,不是对象则返回(只能处理对象)
- 创建拦截器对象 handler,设置 get / set /deleteProperty
- 返回 Proxy 对象
// 判断是否为对象
const isObject = val => val !== null && typeof val === 'object'
// 对象的键仍为对象继续调用 reactive 处理
const convert = target => isObject(target) ? reactive(target) ? target
// 判断对象是否有某个属性
const hasOwnProperty = Object.prototype.hasOwnProperty
// call 方法相当于 target.hasOwnProperty(key)
const hasOwn = (target, key) => hasOwnProperty.call(target, key)
// reactive 方法
export function reactive(target) {
if(!isObject(target)) return target
const handler = {
get(target, key, receiver) {
// 收集依赖
track(target, key)
const result = Reflect.get(target, key, receiver)
return convert(result)
},
set(target, key, value, receiver) {
const oldValue = Reflect.get(target, key, receiver)
let result = true // 返回 boolean 值
if (oldValue !== value) {
result = Reflect.set(target, key, value, receiver)
// 触发更新
trigger(target, key)
}
return result
},
deleteProperty(target, key) {
const hadKey = hasOwn(target, key)
const result = Reflect.deleteProperty(target, key)
if (hadKey && result) {
// 触发更新
trigger(target, key)
}
}
}
return new Proxy(target, handler)
}
测试
import { reactive } from './index.js'
const obj = reactive({
name: 'zs',
age: 18
})
obj.name = 'ls'
delete obj.age
console.log(obj)
# 收集依赖
如何收集依赖,先看一个例子
import { reactive, effect } from './reactivity/index.js'
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
let total = 0
effect(() => {
total = product.price * product.count
// 此处访问时会执行 price 和 count 属性的 get 方法,收集依赖
})
console.log(total)
product.price = 4000 // 此处访问时会执行 price 和 count 属性的 set 方法,触发更新
console.log(total)
product.count = 1
console.log(total)
# 收集依赖实现:effect && track
let activeEffect = null
export function effect(callback) {
activeEffect = callback
callback() // 访问响应式对象属性,收集依赖
activeEffect = null
}
let targetMap = new WeakMap()
export function track(target, key) {
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)
}
# 触发更新:trigger
export function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(() => {
effect()
})
}
}
# ref
export function ref(raw) {
// 判断 raw 是否是 ref 创建的对象,如果是直接返回
if (isObject(raw) && raw.__v_isRef) {
return
}
let value = convert(raw)
const r = {
__v_isRef: true,
get value() {
track(r, 'value')
return value
},
set value(newValue) {
if (newValue !== value) {
raw = newValue
value = convert(raw)
trigger(r, 'value')
}
}
}
return r
}
ref测试
import { reactive, effect, ref } from './reactivity/index.js'
const price = ref(5000)
const count = ref(3)
let total = 0
effect(() => {
total = price.value * count.value
})
console.log(total)
price.value = 4000
console.log(total)
count.value = 1
console.log(total)
# reactive vs ref
- ref 可以把基本数据类型数据,转成响应式对象
- ref 返回的对象,重新赋值成对象也是响应式的
- reactive 返回的对象,重新赋值丢失响应式
- reactive 返回的对象不可以解构
# toRefs
export function toRefs (proxy) {
const ret = proxy instanceof Array ? new Array(proxy.length) : {}
for (const key in proxy) {
// 把每个属性都转换成 ref 对象
ret[key] = toProxyRef(proxy, key)
}
return ret
}
function toProxyRef (proxy, key) {
const r = {
__v_isRef: true,
get value () {
// 访问的已经是响应式对象,不需要收集依赖
return proxy[key]
},
set value (newValue) {
proxy[key] = newValue
}
}
return r
}
toRefs测试
import { reactive, effect, toRefs } from './reactivity/index.js'
function useProduct () {
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
// 直接返回 product 解构之后不是响应式的
// toRefs 将每个属性转换成了响应式,解构之后依旧是响应式
return toRefs(product)
}
const { price, count } = useProduct()
let total = 0
effect(() => {
total = price.value * count.value
})
console.log(total)
price.value = 4000
console.log(total)
count.value = 1
console.log(total)
# computed
Computed 实际上就是一个 effect 函数,computed 返回的是一个 ref 对象。
// 返回 ref 创建的对象
export function computed (getter) {
const result = ref()
effect(() => (result.value = getter()))
return result
}
computed测试
import { reactive, effect, computed } from './reactivity/index.js'
const product = reactive({
name: 'iPhone',
price: 5000,
count: 3
})
let total = computed(() => {
return product.price * product.count
})
console.log(total.value)
product.price = 4000
console.log(total.value)
product.count = 1
console.log(total.value)