仿写 mini-vue reactivity 响应式实现
 · 阅读需 6 分钟
完成一个简版Vue3 Day 01 reactivity 响应式实现
v1
// v1
let a = 10
let b = a + 10
console.log(b)
a = 20
b = a + 10
console.log(b)
v2
// v2
let a = 10
let b
update()
function update() {
  b = a + 10
  console.log(b)
}
a = 20
update()
v3
npm init

npm i @vue/reactivity
+ @vue/reactivity@3.0.5
added 2 packages from 1 contributor and audited 2 packages in 8.981s
found 0 vulnerabilities
const { effect, reactive } = require('@vue/reactivity')
// v3
// a 发生变更了,我想让 b 自动更新
// 声明一个响应式对象
let a = reactive({
    value: 1,
})
let b
effect(() => {
    // 函数
    // 1. 会执行以下
    b = a.value + 10
    console.log(b)
})
// a 响应式对象的值发生改变之后 再次执行
a.value = 30
响应式库
// 响应式库
// 依赖
let currentEffect
class Dep {
    constructor(val) {
        // ES6 +
        this.effects = new Set()
        this._val = val
    }
    get value() {
        return this._val
    }
    
    set value(newVal) {
        this._val = newVal
    }
    // 1. 收集依赖
    depend() {
        if (currentEffect) {
            this.effects.add(currentEffect)
        }
    }
    // 2. 触发依赖
    notice() {
        // 触发一下我们之前收集到的依赖
        this.effects.forEach(effect => {
            effect()
        })
    }
}
function effectWatch (effect) {
    // 收集依赖
    currentEffect = effect
    effect()
    dep.depend()
    currentEffect = null
}
const dep = new Dep(10)
let b
effectWatch(() => {
    console.log("heihei")
    b = dep.value + 10
})
// 值发生变更
dep.value = 20
dep.notice()
heihei 不太明显,换成 b :
effectWatch(() => {
    b = dep.value + 10
    console.log(b)
})
代码优化:
// 响应式库
// 依赖
let currentEffect
class Dep {
    constructor(val) {
        // ES6 +
        this.effects = new Set()
        this._val = val
    }
    get value() {
        this.depend()
        return this._val
    }
    
    set value(newVal) {
        this._val = newVal
        this.notice()
    }
    // 1. 收集依赖
    depend() {
        if (currentEffect) {
            this.effects.add(currentEffect)
        }
    }
    // 2. 触发依赖
    notice() {
        // 触发一下我们之前收集到的依赖
        this.effects.forEach(effect => {
            effect()
        })
    }
}
function effectWatch (effect) {
    // 收集依赖
    currentEffect = effect
    effect()
    // dep.depend()
    currentEffect = null
}
// ref → 很像
const dep = new Dep(10)
let b
effectWatch(() => {
    b = dep.value + 10
    console.log(b)
})
// 值发生变更
dep.value = 20
// dep.notice()
实现 reactive :
// reactive
// dep → number string
// object → key → dep
// 1. 知道这个对象在什么时候改变的
// object.a → get
// object.a = 2 → set
// vue2
// proxy
function reactive (raw) {
    return new Proxy(raw, {
        get(target, key) {
            console.log(key)
        }
    })
}
const user = reactive({
    age: 19,
})
user.age
// 响应式库
// 依赖
let currentEffect
class Dep {
    constructor(val) {
        // ES6 +
        this.effects = new Set()
        this._val = val
    }
    get value() {
        this.depend()
        return this._val
    }
    
    set value(newVal) {
        this._val = newVal
        this.notice()
    }
    // 1. 收集依赖
    depend() {
        if (currentEffect) {
            this.effects.add(currentEffect)
        }
    }
    // 2. 触发依赖
    notice() {
        // 触发一下我们之前收集到的依赖
        this.effects.forEach(effect => {
            effect()
        })
    }
}
function effectWatch (effect) {
    // 收集依赖
    currentEffect = effect
    effect()
    // dep.depend()
    currentEffect = null
}
// ref → 很像
const dep = new Dep(10)
let b
effectWatch(() => {
    b = dep.value + 10
    console.log(b)
})
// 值发生变更
dep.value = 20
// dep.notice()
// reactive
// dep → number string
// object → key → dep
// 1. 知道这个对象在什么时候改变的
// object.a → get
// object.a = 2 → set
// vue2
// proxy
const targetMap = new Map()
function getDep (target, key) {
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        depsMap = new Map()
        targetMap.set(target, depsMap)
    }
    let dep = depsMap.get(key)
    if (!dep) {
        dep = new Dep()
        depsMap.set(key, dep)
    }
    return dep
}
function reactive (raw) {
    return new Proxy(raw, {
        get(target, key) {
            console.log(key)
            // key - dep
            // dep 存储在哪里
            const dep = getDep(target, key)
            // 依赖收集
            dep.depend()
            return Reflect.get(target, key)
        },
        set(target, key, value) {
            // 触发依赖
            // 要获取到 dep
            const dep = getDep(target, key)
            const result = Reflect.set(target, key, value)
            dep.notice()
            return result
        }
    })
}
const user = reactive({
    age: 19,
})
let double
effectWatch(() => {
    console.log("---reactive---")
    double = user.age
    console.log(double)
})
user.age = 20
将 effectWatch 和 reactive 导出:
// 响应式库
// 依赖
let currentEffect
class Dep {
    constructor(val) {
        // ES6 +
        this.effects = new Set()
        this._val = val
    }
    get value() {
        this.depend()
        return this._val
    }
    
    set value(newVal) {
        this._val = newVal
        this.notice()
    }
    // 1. 收集依赖
    depend() {
        if (currentEffect) {
            this.effects.add(currentEffect)
        }
    }
    // 2. 触发依赖
    notice() {
        // 触发一下我们之前收集到的依赖
        this.effects.forEach(effect => {
            effect()
        })
    }
}
export function effectWatch (effect) {
    // 收集依赖
    currentEffect = effect
    effect()
    // dep.depend()
    currentEffect = null
}
// ref → 很像
// const dep = new Dep(10)
// let b
// effectWatch(() => {
//     b = dep.value + 10
//     console.log(b)
// })
// // 值发生变更
// dep.value = 20
// dep.notice()
// reactive
// dep → number string
// object → key → dep
// 1. 知道这个对象在什么时候改变的
// object.a → get
// object.a = 2 → set
// vue2
// proxy
const targetMap = new Map()
function getDep (target, key) {
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        depsMap = new Map()
        targetMap.set(target, depsMap)
    }
    let dep = depsMap.get(key)
    if (!dep) {
        dep = new Dep()
        depsMap.set(key, dep)
    }
    return dep
}
export function reactive (raw) {
    return new Proxy(raw, {
        get(target, key) {
            console.log(key)
            // key - dep
            // dep 存储在哪里
            const dep = getDep(target, key)
            // 依赖收集
            dep.depend()
            return Reflect.get(target, key)
        },
        set(target, key, value) {
            // 触发依赖
            // 要获取到 dep
            const dep = getDep(target, key)
            const result = Reflect.set(target, key, value)
            dep.notice()
            return result
        }
    })
}
// const user = reactive({
//     age: 19,
// })
// let double
// effectWatch(() => {
//     console.log("---reactive---")
//     double = user.age
//     console.log(double)
// })
// user.age = 20
去掉原来文件中引用的官方文件,引入自己编写的文件,并修改:
// reactivity 响应式实现
// const { effect, reactive } = require('@vue/reactivity')
const { effectWatch, reactive } = require('./core/reactivity')
// v1
// let a = 10
// let b = a + 10
// console.log(b)
// a = 20
// b = a + 10
// console.log(b)
// v2
// let a = 10
// let b
// update()
// function update() {
//     b = a + 10
//     console.log(b)
// }
// a = 20
// update()
// v3
// a 发生变更了,我想让 b 自动更新
// 声明一个响应式对象
let a = reactive({
    value: 1,
})
let b
effectWatch(() => {
    // 函数
    // 1. 会执行以下
    b = a.value + 10
    console.log(b)
})
// a 响应式对象的值发生改变之后 再次执行
a.value = 30
报错

创建 index.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" src="./index.js"></script>
</body>
</html>
修改 js 文件中引入文件 由 require 改为 import :
// const { effectWatch, reactive } = require('./core/reactivity')
import { effectWatch, reactive } from './core/reactivity'
执行 html 文件
报错:
Failed to load module script: The 127.0.0.1/:1 server responded with a non-JavaScript MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
将 js 中的 import 写全:
import { effectWatch, reactive } from './core/reactivity/index.js'
重新执行 html 文件,效果如图:
项目仓库: 点此访问