Skip to content

effect

  • 将函数和数据进行关联,点那个数据发生变化时,所对应的函数会重新执行
js
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

第一个版本

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

js

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)] } }

第一个版本发现的问题

js
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 的判断

image

解决方式

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

js
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 应该删除的。

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

js
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) {
  let deps = environment.deps // 当前环境函数的依赖数组
  if (deps.length) { 
    deps.forEach((dep) => { 
      dep.delete(environment) 
      if (dep.size === 0) { 
        for (let [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 导致无限循环

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

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

解决方案:

js
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] } }
// }

第三个版本

js
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

解决方案: 模拟入栈出栈

js
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] } }
// }

最后更新时间: