2024.05.14

vue 响应式实现(4)

effect

  • 将函数和数据进行关联,点那个数据发生变化时,所对应的函数会重新执行
const data = {
  a: 1,
  b: 2,
  c: 3
}

const state = new Proxy(data, {
  get(target, key) {
    const result = Reflect.get(target, key)
    track(target, key)
    return result
  },
  set(target, key, value) {
    const result = Reflect.set(target, key, value)
    trigger(target, key, value)
    return result
  }
})

function track(target, key) {
  console.log('track', target, key)
}

function trigger(target, key, value) {
  console.log('trigger', target, key, value)
}

function effect(fn) {
  fn()
}

effect(() => {
  state.a
})

state.a = 111

第一个版本

每一个属性对应set集合,包含依赖的函数,属性存在于 map 结构中。由一个变量来控制函数保存

const data = {
  a: 1,
  b: 2,
  c: 3
}

// 记录当前的回掉函数
let currentEffect = null// 对应数据的 key,结构为: {a: Set()}
// 可能一个 key 对应多个 effect 函数
const depsMap = new Map() const state = new Proxy(data, {
  get(target, key) {
    const result = Reflect.get(target, key)
    track(target, key)
    return result
  },
  set(target, key, value) {
    const result = Reflect.set(target, key, value)
    trigger(target, key, value)
    return result
  }
})

function track(target, key) {
  // 在进行依赖收集的时候,建立数据与函数的一个关系  if (currentEffect) {    let deps = depsMap.get(key)     if (!deps) {      depsMap.set(key, (deps = new Set()))     }    deps.add(currentEffect)   }  console.log(depsMap)
}

function trigger(target, key, value) {
  // 设置的时候将对应关联的函数重新执行
  const deps = depsMap.get(key)   if (deps) {    deps.forEach(effect => effect())   }}
// 记录当前 函数,运行后重置
function effect(fn) {
  currentEffect = fn  fn()   currentEffect = null}
effect(() => {
  state.a
})

state.a = 111

// depsMap:
// Map(1) { 'a' => Set(1) { [Function (anonymous)] } }
// Map(1) { 'a' => Set(1) { [Function (anonymous)] } }

第一个版本发现的问题

effect(() => {
  console.log('effect 函数执行了')
  if (state.a === 1) {
    return state.b
  }
  else {
    return state.c
  }
})
state.a = 111
// depsMap:
// effect 函数执行了
// Map(1) { 'a' => Set(1) { [Function (anonymous)] } }
// Map(2) {
//   'a' => Set(1) { [Function (anonymous)] },
//   'b' => Set(1) { [Function (anonymous)] }
// }
// effect 函数执行了
// Map(2) {
//   'a' => Set(1) { [Function (anonymous)] },
//   'b' => Set(1) { [Function (anonymous)] }
// }
// Map(2) {
//   'a' => Set(1) { [Function (anonymous)] },
//   'b' => Set(1) { [Function (anonymous)] }
// }

第二个版本

在修改后依赖错误

问题原因:

第一次建立依赖关系的时候,是将依赖的函数作为 currentEffect。 通过 currentEffect 将依赖函数添加到依赖的 map 中

随后函数执行完 currentEffect 重置为了 null,之后在属性发生变化的时候,重新运行的是回掉函数,但是 currentEffect 依然是 null,所以走不进 track 的判断

解决方式

回掉函数增加环境信息,记录一下这个环境函数在哪个集合中使用

function track(target, key) {
  if (currentEffect) {
    let deps = depsMap.get(key)
    if (!deps) {
      depsMap.set(key, (deps = new Set()))
    }
    deps.add(currentEffect)
  }
  console.log(depsMap)
}

function trigger(target, key, value) {
  const deps = depsMap.get(key)
  if (deps) {
    deps.forEach(effect => effect())
  }
}

function effect(fn) {
  const environment = () => {    currentEffect = environment
    fn()
    currentEffect = null
  }
  environment() }

// depsMap:
// effect 函数执行了
// Map(1) { 'a' => Set(1) { [Function: environment] } }
// Map(2) {
//   'a' => Set(1) { [Function: environment] },
//   'b' => Set(1) { [Function: environment] }
// }
// effect 函数执行了
// Map(2) {
//   'a' => Set(1) { [Function: environment] },
//   'b' => Set(1) { [Function: environment] }
// }
// Map(3) {
//   'a' => Set(1) { [Function: environment] },
//   'b' => Set(1) { [Function: environment] },
//   'c' => Set(1) { [Function: environment] }
// }

依赖还多了捏 b 应该删除的。

决绝方案: 函数执行前清楚依赖

function track(target, key) {
  if (currentEffect) {
    let deps = depsMap.get(key)
    if (!deps) {
      depsMap.set(key, (deps = new Set()))
    }
    currentEffect.deps.push(deps)     deps.add(currentEffect)
  }
  console.log(depsMap)
}

function cleanup(environment) {
  const deps = environment.deps // 当前环境函数的依赖数组  if (deps.length) {    deps.forEach((dep) => {      dep.delete(environment)       if (dep.size === 0) {        for (const [key, value] of depsMap) {          if (value === dep) {            depsMap.delete(key)           }        }      }    })     deps.length = 0  }}

function trigger(target, key, value) {
  const deps = depsMap.get(key)
  if (deps) {
    deps.forEach(effect => effect())
  }
}

function effect(fn) {
  const environment = () => {
    currentEffect = environment
    cleanup() // 清除旧的依赖    fn()
    currentEffect = null
  }
  environment.deps = [] // 记录一下环境函数在哪些集合中使用  environment()
}

// effect 函数执行了
// Map(1) { 'a' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(2) {
//   'a' => Set(1) { [Function: environment] { deps: [Array] } },
//   'b' => Set(1) { [Function: environment] { deps: [Array] } }
// }
// effect 函数执行了
// Map(1) { 'a' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(2) {
//   'a' => Set(1) { [Function: environment] { deps: [Array] } },
//   'c' => Set(1) { [Function: environment] { deps: [Array] } }
// }

第二个版本发现的问题

无限循环

问题原因:

在 track 函数中,每次 state.a 在访问时,都会重新添加当前的 activeEffect 到依赖集合中

在 trigger 函数中,修改 state.a,会触发所有 state.a 的 effect 函数,这些函数又访问了 state.a 导致无限循环

effect(() => {
  if (state.a === 1) {
    state.b
  }
  else {
    state.c
  }
})

effect(() => {
  state.a
  state.c
})
state.a = 111

解决方案:

function trigger(target, key, value) {
  // 设置的时候将对应关联的函数重新执行
  const deps = depsMap.get(key)
  if (deps) {
    // 复制一份,避免循环引用
    const effectToRun = new Set(deps)     effectToRun.forEach(effect => effect())   }
}
// Map(1) { 'a' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(2) {
//   'a' => Set(1) { [Function: environment] { deps: [Array] } },
//   'b' => Set(1) { [Function: environment] { deps: [Array] } }
// }
// Map(2) {
//   'a' => Set(2) {
//     [Function: environment] { deps: [Array] },
//     [Function: environment] { deps: [Array] }
//   },
//   'b' => Set(1) { [Function: environment] { deps: [Array] } }
// }
// Map(3) {
//   'a' => Set(2) {
//     [Function: environment] { deps: [Array] },
//     [Function: environment] { deps: [Array] }
//   },
//   'b' => Set(1) { [Function: environment] { deps: [Array] } },
//   'c' => Set(1) { [Function: environment] { deps: [Array] } }
// }

第三个版本

effect(() => {
  effect(() => {
    effect(() => {
      state.c
    })
  })
  state.b
  state.a
})
// Map(1) { 'c' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(1) { 'c' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(1) { 'c' => Set(1) { [Function: environment] { deps: [Array] } } }

问题原因:

问题原因是 执行到effect到时候,给 currentEffect = null

解决方案: 模拟入栈出栈

const effectStack = [] const environment = () => {
  currentEffect = environment
  effectStack.push(environment)   cleanup(environment)
  fn()
  effectStack.pop()
  currentEffect = effectStack[effectStack.length-1]   // currentEffect = null}
environment.deps = []
environment()
}
// Map(1) { 'c' => Set(1) { [Function: environment] { deps: [Array] } } }
// Map(2) {
//   'c' => Set(1) { [Function: environment] { deps: [Array] } },
//   'b' => Set(1) { [Function: environment] { deps: [Array] } }
// }
// Map(3) {
//   'c' => Set(1) { [Function: environment] { deps: [Array] } },
//   'b' => Set(1) { [Function: environment] { deps: [Array] } },
//   'a' => Set(1) { [Function: environment] { deps: [Array] } }
// }