图解Vue3 响应式,手动实现核心原理

响应式 = 同一份引用 + 依赖跟踪 + 变更通知
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

包装target (对象/数组/函数甚/proxy对象)

handler

被代理对象上的自定义行为(定义一组处理函数(例如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.get
  • Proxy的set对应Reflect.set

属性访问方法

A.属性访问器(访问):obj.key,obj[key]

B.函数调用:mp.get(key)

Reflect.get(target, propertyKey[, receiver])函数调用方式从对象中读值

receiver

如果target对象中指定了getterreceiver则为getter调用时的this

该方法会拦截目标对象的以下操作:

  • 访问属性:proxy[foo] 和 proxy.bar
  • 访问原型链上的属性:Object.create(proxy)[foo]
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)

参考链接

林三心画了8张图,最通俗易懂的Vue3响应式核心原理解析 - 掘金

相关推荐
尘世中一位迷途小书童4 小时前
Vuetify Admin 后台管理系统
前端·前端框架·开源
2301_801252224 小时前
前端框架Vue(Vue 的挂载点与 data 数据对象)
java·前端·javascript·vue.js·前端框架
资讯第一线5 小时前
《Windows Server 2022》 [2025年10月版 ] [官方IOS] 下载
前端
非凡ghost5 小时前
EaseUS Fixo(易我视频照片修复)
前端·javascript·后端
非凡ghost5 小时前
Avast Cleanup安卓版(手机清理优化)
前端·javascript·后端
豆苗学前端5 小时前
长时间不操作自动退出登录(系统非活跃状态下自动登出机制的企业级设计方案)
前端·后端·面试
一乐小哥5 小时前
用 AI 搞出 Chrome 版 “飞书 Command+K”!Figma AI 救了我的审美
前端·ai编程
非凡ghost5 小时前
Atlantis Word Processor(文字处理软件)
前端·javascript·后端
小时前端5 小时前
面试官:线上应用内存持续泄漏,你如何快速定位并止血?
前端·浏览器