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
}
相关推荐
bysking23 分钟前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓39 分钟前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_41142 分钟前
无网络安装ionic和运行
前端·npm
理想不理想v43 分钟前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云1 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205871 小时前
web端手机录音
前端
齐 飞1 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹1 小时前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
GIS程序媛—椰子2 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_0012 小时前
前端八股文(一)HTML 持续更新中。。。
前端·html