Vue3响应式系统原理

Vue3 响应式回顾

  • Proxy 对象实现属性监听
  • 多层属性嵌套,在访问属性过程中,处理下一级属性
  • 默认监听动态添加的属性
  • 默认监听属性的删除操作
  • 默认监听数组的索引和length 属性
  • 可以作为单独的模块使用

核心方法

reactive/effect/track/trigger/ref/toRefs/computed

  • reactive

    • 接收一个参数,判断这个参数是否是对象
    • 创建一个拦截器对象 handler, 设置get/set/deleteProperty
    • 返回Proxy对象
    scss 复制代码
    export function reactive(target) {
    if(!isObject(target)) return target
    
    // 处理器或者拦截器对象 handle 对象
    const  handler = {
        get (target, key, receiver) {
            // 收集依赖
            track(target, key)
            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 = true
            if(oldValue !== value) {
             result = Reflect.set(target, key, value, receiver)
                // 触发更新
                trigger(target, key)
                console.log('set', key, value)
                // set 方法需要返回一个 布尔类型的值,标识着我们的赋值是否成功
            }
            return result
        },
        deleteProperty (target, key ) {
            const hasKey = hasOwn(target, key)
            const result = Reflect.deleteProperty(target, key)
            if(hasKey && result) {
                // 触发更新
                trigger(target, key)
                console.log('delete', key)
            }
            return result
        }
    }
    
    return new Proxy(target, handler)
    }
  • effect

    • effect 函数用于定义副作用,参数就是副作用函数,当响应式数据变化后,会导致副作用函数重新执行
javascript 复制代码
let activeEffect = null

// 额外
// effect 函数用于定义副作用,参数就是副作用函数,当响应式数据变化后,会导致副作用函数重新执行

export function effect (callback) {
    activeEffect = callback
    // 会首先执行一次 callback
    callback()  // callback 会访问响应式对象的属性,这个过程中去收集依赖,存储callback
    // 依赖收集完毕之后  收集依赖的时候,如果有嵌套属性的话 是一个递归的过程  所以需要设置为 null
    activeEffect = null
}
  • track
    • track 是收集依赖(收集effect) trigger是触发更新 (执行effect)
scss 复制代码
let targetMap =  new WeakMap()
//  track 是收集依赖(收集effect) trigger是触发更新 (执行effect)
export function track (target, key) {
    if(!activeEffect) return

    let depsMap = targetMap.get(target)
    // 如果 depsMap 没有值,说明没有收集过依赖,没有创建过对应的 Map 对象 需要新建一个
    if(!depsMap){
        targetMap.set(target, (depsMap = new Map()))
    }
    // 根据属性查找对应的dep 对象
    let dep = depsMap.get(key)
    //判断dep是否存在,这个集合是用来存储 属性对应的effect 函数 没有找到  创建一个新的dep集合 添加到 depsMap
    if(!dep) {
        depsMap.set(key, (dep = new Set()))
    }

    // 把effect 函数添加到 dep集合中
    dep.add(activeEffect)

}
  • trigger
scss 复制代码
export function trigger (target, key) {
    // 根据target 去 targetMap 中找到 depsMap
    // depsMap  中存储的是属性和对应的 dep 集合  dep集合中存储的就是属性对应的那些 effect 函数
    const depsMap = targetMap.get(target)

    if(!depsMap) return

    const dep = depsMap.get(key)
    //  dep存在,遍历里面所有的 effect 函数 并进行调用
    if(dep){
        dep.forEach(effect => {
            effect()
        })
    }

}
  • ref

    • 判断 raw 是否是 ref 创建的对象, 如果是的话直接返回
    • 判断 raw 是否是对象 如果是对象 调用reactive 创建响应式对象 否则
    • 判断 raw 是否是对象 如果是对象 调用reactive 创建响应式对象 否则直接返回
    • 不管他是什么值,都需要创建一个ref的对象
    • 这个对象有get set 只有value属性 还有一个标识,证明他是否是ref创建的对象
      • get中 需要调用track 收集依赖 属性是value属性,因为这个对象只有一个value属性
      • set中 首先需要判断 新值和旧值是否相等 如果新值不等于旧值 那么把新值存储到raw中 还需要判断新赋值的raw是否是对象
    scss 复制代码
    export function ref (raw) {
    // 判断 raw 是否是 ref 创建的对象, 如果是的话直接返回
    if(isObject(raw) && raw.__V_isRef) {
        return
    }
    // 判断 raw 是否是对象 如果是对象  调用reactive 创建响应式对象  否则直接返回
    let value = convert(raw) 
    
    // 不管他是什么值,都需要创建一个ref的对象 
    // 这个对象有get set 只有value属性 还有一个标识,证明他是否是ref创建的对象
    const r = {
        __V_isRef: true,
        get value () {
            // 需要调用track 收集依赖  属性是value属性,因为这个对象只有一个value属性
            track(r, 'value')
            return value
        },
        set value (newValue) {
            // 首先需要判断  新值和旧值是否相等 如果新值不等于旧值 那么把新值存储到raw中 还需要判断新赋值的raw是否是对象
            if(newValue !== value){
                raw = newValue
                value = convert(raw)
                trigger(r, 'value')
            }
        }
    }
    }
  • toRefs

    • 作用是 把reactive 返回的对象的每一个属性转换成类似于ref返回的对象,这样我们可以对 reactive 返回的对象进行解构
      • 创建一个ret 传过来的参数 数组的话 创建一个长度是 length 的数组 否则返回空对象
      • 遍历 proxy 对象所有的属性 数组就是遍历索引,把每个属性转化成类似于ref 返回的对象
csharp 复制代码
    // 作用是 把reactive 返回的对象的每一个属性转换成类似于ref返回的对象,这样我们可以对 reactive 返回的对象进行解构
export function toRefs (proxy) {
    // 判断这个参数是不是reactive 创建的对象,如果不是的话,发送警告

    // ret 传过来的参数 数组的话  创建一个长度是 length 的数组 否则返回空对象
    const  ret = proxy instanceof Array ? new Array(proxy.length) : {}
    // 遍历 proxy 对象所有的属性  数组就是遍历索引,把每个属性转化成类似于ref 返回的对象
    for (const key in proxy) {
      ret[key] = toProxyRef(proxy, key) //  把所有转换好的属性  存储到ret 对象中来
    }

    return ret
}
function toProxyRef(proxy, key) {
    // 这个函数里面创建一个对象  最终返回这个对象
    const r = {
        __V_isRef: true,
        get value () {
            return proxy[key]
            // 这里不需要去收集依赖,这里访问的就是一个响应式对象,当访问他的key属性的时候,proxy内部的get会去收集依赖
        },
        set value (newValue) {
            proxy[key] =  newValue
        }
    }
    return r
}
  • computed
    • computed 需要接收一个有返回值的函数作为参数 这个函数的返回值就是计算属性的值 ,并且要监听整个函数内部使用的响应式数据的变化,最后把这个函数执行的结果返回
scss 复制代码
// computed 需要接收一个有返回值的函数作为参数 这个函数的返回值就是计算属性的值 ,并且要监听整个函数内部使用的响应式数据的变化,最后把这个函数执行的结果返回

export function computed (getter) {
    // 返回一个 ref 创建的 具有 value 属性的对象
    const result = ref()  // ref 不需要传入参数, 如果不传参数的话,默认传入的undefined
    
    // 调用getter 函数,把 getter() 的结果存储到 result.value 中
    effect(()=>(result.value = getter()))
    // computed 是通过 effect 监听整个函数内部使用的响应式数据的变化, 
    // 因为在 effect 中 执行getter的时候,访问响应式数据的属性,会去收集依赖,
    // 当数据发生变化之后,会去重新执行 effect函数,把getter的结果重新存储到result 中
    return result
}
scss 复制代码
const isObject = val => val !== null && typeof val === 'object';
const convert = target => isObject(target) ? reactive(target) : target

const hasOwnProperty = Object.prototype.hasOwnProperty

const hasOwn = (target, key) => hasOwnProperty.call(target,key)
// 通过call 修改hasOwnProperty 里面的this 指向

// reactive 返回的对象 是一个 proxy 实例 传入的必须是一个对象
export function reactive(target) {
    if(!isObject(target)) return target

    // 处理器或者拦截器对象 handle 对象
    const  handler = {
        get (target, key, receiver) {
            // 收集依赖
            track(target, key)
            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 = true
            if(oldValue !== value) {
             result = Reflect.set(target, key, value, receiver)
                // 触发更新
                trigger(target, key)
                console.log('set', key, value)
                // set 方法需要返回一个 布尔类型的值,标识着我们的赋值是否成功
            }
            return result
        },
        deleteProperty (target, key ) {
            const hasKey = hasOwn(target, key)
            const result = Reflect.deleteProperty(target, key)
            if(hasKey && result) {
                // 触发更新
                trigger(target, key)
                console.log('delete', key)
            }
            return result
        }
    }

    return new Proxy(target, handler)
}


let activeEffect = null

// 额外
// effect 函数用于定义副作用,参数就是副作用函数,当响应式数据变化后,会导致副作用函数重新执行

export function effect (callback) {
    activeEffect = callback
    // 会首先执行一次 callback
    callback()  // callback 会访问响应式对象的属性,这个过程中去收集依赖,存储callback
    // 依赖收集完毕之后  收集依赖的时候,如果有嵌套属性的话 是一个递归的过程  所以需要设置为 null
    activeEffect = null
}


let targetMap =  new WeakMap()
//  track 是收集依赖(收集effect) trigger是触发更新 (执行effect)
export function track (target, key) {
    if(!activeEffect) return

    let depsMap = targetMap.get(target)
    // 如果 depsMap 没有值,说明没有收集过依赖,没有创建过对应的 Map 对象 需要新建一个
    if(!depsMap){
        targetMap.set(target, (depsMap = new Map()))
    }
    // 根据属性查找对应的dep 对象
    let dep = depsMap.get(key)
    //判断dep是否存在,这个集合是用来存储 属性对应的effect 函数 没有找到  创建一个新的dep集合 添加到 depsMap
    if(!dep) {
        depsMap.set(key, (dep = new Set()))
    }

    // 把effect 函数添加到 dep集合中
    dep.add(activeEffect)

}

export function trigger (target, key) {
    // 根据target 去 targetMap 中找到 depsMap
    // depsMap  中存储的是属性和对应的 dep 集合  dep集合中存储的就是属性对应的那些 effect 函数
    const depsMap = targetMap.get(target)

    if(!depsMap) return

    const dep = depsMap.get(key)
    //  dep存在,遍历里面所有的 effect 函数 并进行调用
    if(dep){
        dep.forEach(effect => {
            effect()
        })
    }

}


export function ref (raw) {
    // 判断 raw 是否是 ref 创建的对象, 如果是的话直接返回
    if(isObject(raw) && raw.__V_isRef) {
        return
    }
    // 判断 raw 是否是对象 如果是对象  调用reactive 创建响应式对象  否则
    let value = convert(raw) 

    // 不管他是什么值,都需要创建一个ref的对象 
    // 这个对象有get set 只有value属性 还有一个标识,证明他是否是ref创建的对象
    const r = {
        __V_isRef: true,
        get value () {
            // 需要调用track 收集依赖  属性是value属性,因为这个对象只有一个value属性
            track(r, 'value')
            return value
        },
        set value (newValue) {
            // 首先需要判断  新值和旧值是否相等 如果新值不等于旧值 那么把新值存储到raw中 还需要判断新赋值的raw是否是对象
            if(newValue !== value){
                raw = newValue
                value = convert(raw)
                trigger(r, 'value')
            }
        }
    }
}

// 作用是 把reactive 返回的对象的每一个属性转换成类似于ref返回的对象,这样我们可以对 reactive 返回的对象进行解构
export function toRefs (proxy) {
    // 判断这个参数是不是reactive 创建的对象,如果不是的话,发送警告

    // ret 传过来的参数 数组的话  创建一个长度是 length 的数组 否则返回空对象
    const  ret = proxy instanceof Array ? new Array(proxy.length) : {}
    // 遍历 proxy 对象所有的属性  数组就是遍历索引,把每个属性转化成类似于ref 返回的对象
    for (const key in proxy) {
      ret[key] = toProxyRef(proxy, key) //  把所有转换好的属性  存储到ret 对象中来
    }

    return ret
}


function toProxyRef(proxy, key) {
    // 这个函数里面创建一个对象  最终返回这个对象
    const r = {
        __V_isRef: true,
        get value () {
            return proxy[key]
            // 这里不需要去收集依赖,这里访问的就是一个响应式对象,当访问他的key属性的时候,proxy内部的get会去收集依赖
        },
        set value (newValue) {
            proxy[key] =  newValue
        }
    }
    return r
}

// computed 需要接收一个有返回值的函数作为参数 这个函数的返回值就是计算属性的值 ,并且要监听整个函数内部使用的响应式数据的变化,最后把这个函数执行的结果返回

export function computed (getter) {
    // 返回一个 ref 创建的 具有 value 属性的对象
    const result = ref()  // ref 不需要传入参数, 如果不传参数的话,默认传入的undefined
    
    // 调用getter 函数,把 getter() 的结果存储到 result.value 中
    effect(()=>(result.value = getter()))
    // computed 是通过 effect 监听整个函数内部使用的响应式数据的变化, 
    // 因为在 effect 中 执行getter的时候,访问响应式数据的属性,会去收集依赖,
    // 当数据发生变化之后,会去重新执行 effect函数,把getter的结果重新存储到result 中
    return result
}
相关推荐
加勒比海涛12 分钟前
ElementUI 布局——行与列的灵活运用
前端·javascript·elementui
你不讲 wood16 分钟前
postcss 插件实现移动端适配
开发语言·前端·javascript·css·vue.js·ui·postcss
前端小程42 分钟前
使用vant UI实现时间段选择
前端·javascript·vue.js·ui
whyfail1 小时前
React 事件系统解析
前端·javascript·react.js
禾苗种树1 小时前
element form rules 验证数组对象属性时如何写判断规则
javascript·vue.js·elementui
Bulut09071 小时前
Vue的slot插槽(默认插槽、具名插槽、作用域插槽)
vue.js·具名插槽·slot插槽·默认插槽·作用域插槽
小tenten2 小时前
js延迟for内部循环方法
开发语言·前端·javascript
程序员大金2 小时前
基于SpringBoot+Vue+MySQL的垃圾分类回收管理系统
java·vue.js·spring boot·后端·mysql·mybatis
幻影浪子2 小时前
Web网站常用测试工具
前端·测试工具
计算机学姐2 小时前
基于Python的可视化在线学习系统
开发语言·vue.js·后端·python·学习·mysql·django