Vue 3 响应式回顾
- Proxy 对象实现属性监听
- 多层属性嵌套,在访问过程中处理下一级属性
- 默认监听动态添加的属性
- 默认监听属性的删除操作
- 默认监听数组索引和 length 属性
- 可以作为单独的模块使用
Proxy 和 Reflect
Proxy 可以对目标对象的读取和修改等操作进行拦截,然后进行处理。创建 Proxy 对象需要两个参数,一个是目标对象,第二个是包含 get、set、deleteProperty 函数的拦截器对象,目标对象的 getter/setter,deleteProperty就设置成这三个函数。
在 Vue 3 中,reactive 的实现就是通过 Proxy 配合 Reflect 实现。
Reflect 也是 ES6 中新增的 API,可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的,例如 get、set、deleteProperty。
在严格模式下,Proxy 中传入的 set 和 deleteProperty 需要返回布尔类型的值,如果返回 false 或者不返回的话会抛出 Type Error 的异常。
'use strict'const target = {foo: 'xxx',bar: 'yyy'}// Reflect.getPrototypeOf()// Object.getPrototypeOf()const proxy = new Proxy(target, {get (target, key, receiver) {// return target[key]return Reflect.get(target, key, receiver)},set (target, key, value, receiver) {// target[key] = valuereturn Reflect.set(target, key, value, receiver)},deleteProperty (target, key) {// delete target[key]return Reflect.deleteProperty(target, key)}})
Proxy 和 Reflect 中使用的 receiver 有区别
Proxy 中 receiver:Proxy 或者继承 Proxy 的对象
Reflect 中 receiver:如果 target 对象中设置了 getter,getter 中的 this 指向 receiver
'use strict'const obj = {get foo() {console.log(this)return this.bar}}const proxy = new Proxy(obj, {get (target, key, receiver) {if (key === 'bar') {return 'value - bar'}// return Reflect.get(target, key) // this 指向 objreturn Reflect.get(target, key, receiver) // this 指向 receiver,在这里就是 proxy 对象}})console.log(proxy.foo) // value-bar
reactive 的手写实现
- 接收一个参数,判断这参数是否对象。reactive 只能处理对象,这和 Vue 2 不同
- 创建拦截器对象 handler,设置 get/set/deleteProperty
- 返回 Proxy 对象 ```javascript const isObject = val => val !== null && typeof val === ‘object’ const convert = target => isObject(target) ? reactive(target) : target
export function reactive(target) { if (!isObject(target)) return
const handler = { get (target, key, receiver) { // 收集依赖 console.log(‘get’, 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 = trueif (oldValue !== value) {result = Reflect.set(target, key, value, receiver)// 触发更新console.log('set', key, value)}return result},deleteProperty (target, key) {let result = trueif (Reflect.has(target, key)) {result = Reflect.deleteProperty(target, key)console.log('delete', key);}return result}
}
return new Proxy(target, handler) }
demo:```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></head><body><script type="module">import { reactive } from './reactivity/index.js'const obj = reactive({name: '张三',age: 18})obj.name = 'lisa'delete obj.ageconsole.log(obj)</script></body></html>
收集依赖
当访问响应式对象属性时,getter 里要收集依赖。修改对象属性时,set 和 deleteProperty 里要触发更新。
- 新建一个 WeakMap,在里面存储键值对,键是被访问的响应式目标对象,值是一个 Map 对象。
- Map 也是一个存储键值对的对象,在这里的作用是存储依赖于目标对象的属性的集合。所以 Map 对象的键是属性名,值是一个 Set 集合,里面存储了对属性进行操作的函数。
- get 中收集依赖的过程大概如下:
- 判断调用 get 的函数是否为空,如果为空直接返回
- 从 WeakMap 中获取以当前对象为键的键值对的值,也就是 Map,如果没有,调用 WeakMap 的 set 方法创建出来。
- 从 Map 中获取以当前属性的名称为键的键值对的值,也就是 Set,如果没有,调用 Map 的 set 方法创建出来。
- 在 Set 中添加函数。
- 触发更新的过程大概如下:
- 从 WeakMap 中获取以当前对象为键的键值对的值,也就是 Map,如果没有,直接返回
- 从 Map 中获取以当前属性的名称为键的键值对的值,也就是 Set,如果有,从里面获取每一个函数并执行。

const isObject = val => val !== null && typeof val === 'object'const convert = target => isObject(target) ? reactive(target) : targetexport function reactive(target) {if (!isObject(target)) returnconst 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 = trueif (oldValue !== value) {result = Reflect.set(target, key, value, receiver)// 触发更新trigger(target, key)}return result},deleteProperty (target, key) {let result = trueif (Reflect.has(target, key)) {result = Reflect.deleteProperty(target, key)result && trigger(target, key)}return result}}return new Proxy(target, handler)}let activeEffect = nullexport function effect (callback) {activeEffect = callbackcallback() // 访问响应式对象属性,去收集依赖activeEffect = null}let targetMap = new WeakMap()/*** 对象的属性收集依赖* @param {object} target 跟踪对象* @param {string} key 属性名*/export function track (target, key) {if (!activeEffect) returnlet 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)}/*** 触发更新* @param {*} target* @param {*} key*/export function trigger (target, key) {const depsMap = targetMap.get(target)if (!depsMap) returnconst dep = depsMap.get(key)if (!dep) returndep.forEach(effect => {effect()})}
demo:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><script type="module">import { reactive, effect } from './reactivity/index.js'const product = reactive({name: 'iPhone',price: 5000,count: 3})let total = 0effect(() => {total = product.price * product.count})console.log(total)product.price = 4000console.log(total)product.count = 1console.log(total)</script></body></html>
ref 的手写实现
ref 把基本数据类型转换成响应式的,如果传入的是对象,调用 reactive。
export function ref (raw) {// 判断 raw 是否是 ref 的对象,如果是的话,直接返回if (isObject(raw) && raw.__v_isRef) return rawlet value = convert(raw)const r = {__v_isRef: true,get value () {track(r, 'value')return value},set value (newValue) {if (newValue !== value) {raw = newValuevalue = convert(raw)trigger(r, 'value')}}}return r}
ref vs reactive
- ref 可以把基本数据类型数据,转成响应式对象
- ref 返回的对象,重新赋值成对象也是响应式的
- reactive 返回的对象,重新赋值丢失响应式
- reactive 返回的对象不可以解构
toRefs
toRefs 接收一个 reactive 返回的 Proxy 对象,如果不是 Proxy 对象,直接返回。
toRefs 会把基本数据类型的属性,处理成像 ref 那种格式,把转换后的属性挂到一个新的对象,返回。
export function toRefs (proxy) {const ret = proxy instanceof Array ? new Array[proxy.length] : {}for (const key in proxy) {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}
computed
computed 接收一个 getter 函数,需要在里面收集依赖。
export function computed (getter) {const result = ref()effect(() => (result.value = getter()))return result}
