响应式

原始值响应

非原始值响应 (对象、数组)

基本响应式

通过 Proxy 代理来实现一个简单的响应式

let fns = new Set(), activeFn
let data = {
    text: '1'
}

const obj = new Proxy(data, {
    get(target, key){
        fns.add(activeFn)
        return target[key]
    },

    set(target, key, newVal){
        target[key] = newVal
        fns.forEach(fn => fn())
    }
})

function effect(fn){
    activeFn = fn
    fn()
}

effect(() => {
    console.log('run time', new Date())
    document.body.innerText = obj.text
})


setTimeout(() => {
    obj.text = 'next'
}, 2000)

常规响应式

基本响应式存在的问题

  • 对于不存在属性,也会执行副作用函数
  • 数据结构导致的垃圾回收问题
let weakMap = new WeakMap()
let activeEffect

let data = {
    text: 'hello'
}

const obj = new Proxy(data, {
    get(target, key) {
        track(target, key)

        return target[key]
    },

    set(target, key, newVal){
        target[key] = newVal

        trigger(target,key)
    }
})

function track(target, key){
    if(!activeEffect) return

    let depsMap = weakMap.get(target)

    if(!depsMap) {
        weakMap.set(target, (depsMap = new Map()))
    }

    let deps = depsMap.get(key)

    if(!deps) {
        depsMap.set(key, (deps = new Set()))
    }

    deps.add(activeEffect)
}

function trigger(target, key, newVal){
    let depsMap = weakMap.get(target)

    if(!depsMap) return

    let effects = depsMap.get(key)

    effects && effects.forEach(fn => fn()) 
}

function effect(fn) {
    activeEffect = fn
    fn()
}

effect(() => {
    console.log('effect run', new Date().now())
    document.body.innerText = obj.text
})

setTimeout(() => {
    obj.text1 = '666'
}, 2000)

进阶响应式

常规响应式存在的问题

  • 分支切换,类似三木运算符
  • effect 副作用函数嵌套
  • 递归循环等
let weakMap = new WeakMap()
let activeEffect

let data = {
    text: 'hello',
    ok: false,
    num: 1
}

const obj = new Proxy(data, {
    get(target, key) {
        track(target, key)

        return target[key]
    },

    set(target, key, newVal){
        target[key] = newVal

        trigger(target,key)
    }
})

function track(target, key){
    if(!activeEffect) return

    let depsMap = weakMap.get(target)

    if(!depsMap) {
        weakMap.set(target, (depsMap = new Map()))
    }

    let deps = depsMap.get(key)

    if(!deps) {
        depsMap.set(key, (deps = new Set()))
    }

    deps.add(activeEffect)
    activeEffect.deps.push(deps)
}

function trigger(target, key, newVal){
    let depsMap = weakMap.get(target)

    if(!depsMap) return

    let effects = depsMap.get(key)
    let effectRun = new Set()

    effects.forEach(effectFn =>{
        // 避免递归
        if(activeEffect !== effectFn){
            effectRun.add(effectFn)
        }
    })

    effectRun && effectRun.forEach(fn => fn()) 
}

// 保存相关依赖的栈
let effectStack = []

function effect(fn) {
    const effectFn = () => {
        cleanup(effectFn)

        activeEffect = effectFn

        effectStack.push(effectFn)
        fn()
        effectStack.pop()

        activeEffect = effectStack[effectStack.length - 1]
    }

    effectFn.deps = []

    effectFn()
}

function cleanup(effectFn){
    for(let i=0;i<effectFn.deps.length;i++){
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }

    effectFn.deps.length = 0
}

effect(() => {
    obj.num = obj.num + 1
})

setTimeout(() => {
    obj.num = 2
}, 2000)

高阶响应式

解决问题

  • 时间调度执行
  • 懒加载
上次更新:
贡献者: Joe