亲宝软件园·资讯

展开

vue开发设计分支切换与cleanup实例详解

凯心 人气:0

分支切换与cleanup

了解分支切换

代码示例如下

const data = { ok: true, text: "hello world" };
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key);
      return target[key];
    },
    // 在set操作中,赋值,然后调用effect函数
    set(target, key, value) {
      target[key] = value;
      trigger(target, key);
      return true;
    },
  });
}
const obj = reactive(data);
effect(function effectFn(){
  document.body.innerText = obj.ok ? obj.text : "not";
});

当代码字段obj.ok发生变化时,代码执行的分支会跟着变化,这就是分支切换

分支切换可能会产生遗留的副作用函数。

上面代码中有个三元运算式,如果obj.ok = true,则展示obj.text,此时,effectFn执行会触发obj.okobj.text的读取操作,否则展示"not"

此时的依赖收集如下图展示:

const data = { ok: true, text: "hello world" };
const obj = reactive(data);
effect(function effectFn(){
  document.body.innerText = obj.ok ? obj.text : "not";
});

分支切换导致的问题

当发生obj.ok改变且为false时,此时obj.text对应的依赖effectFn不会执行,

但是obj.text发生改变时,对应的effectFn却会执行,页面的内容会被修改掉。这是不期望发生的!

此时,是key为ok对应的effectFn依旧有效,

key为text对应的effectFn为无效,应该清除掉,如下图展示

如何清除掉副作用函数的无效关联关系?

步骤:

疑问:为什么对传入的副作用函数进行一层包裹?

function effect(fn) {
  const effectFn = () => {
    activeFn = effectFn;
    cleanup(effectFn);
    fn();
  };
  effectFn.deps = [];
  effectFn();
}
function cleanup(effectFn) {
  // 从副作用函数关联的依赖集合中删除副作用函数,从而断开关联
  for (const deps of effectFn.deps) {
    deps.delete(effectFn);
  }
  // 重置effectFn.deps
  effectFn.deps.length = 0;
}
// 收集effectFn的依赖集合
function track(target, key) {
  if (!activeFn) return target[key];
  let depMap = bucket.get(target);
  if (!depMap) {
    depMap = new Map();
    bucket.set(target, depMap);
  }
  let deps = depMap.get(key);
  if (!deps) {
    deps = new Set();
    depMap.set(key, deps);
  }
  deps.add(activeFn);
  // 收集effectFn的依赖集合
  activeFn.deps.push(deps);
}

完整代码

// 响应式数据的基本实现
let activeFn = undefined;
const bucket = new WeakMap();
let times = 0;
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      console.log(target, key);
      if (times > 10) {
        throw "超出";
      }
      times++;
      console.log(times);
      track(target, key);
      return target[key];
    },
    // 在set操作中,赋值,然后调用effect函数
    set(target, key, value) {
      target[key] = value;
      trigger(target, key);
      return true;
    },
  });
}
// 收集effectFn的依赖集合
function track(target, key) {
  console.log("track");
  if (!activeFn) return target[key];
  let depMap = bucket.get(target);
  if (!depMap) {
    depMap = new Map();
    bucket.set(target, depMap);
  }
  let deps = depMap.get(key);
  if (!deps) {
    deps = new Set();
    depMap.set(key, deps);
  }
  deps.add(activeFn);
  // 收集effectFn的依赖集合
  activeFn.deps.push(deps);
}
function trigger(target, key) {
  const depMap = bucket.get(target);
  if (!depMap) return;
  const effects = depMap.get(key);
  if (!effects) return;
  effects.forEach((fn) => {
    fn();
  });
}
const data = { ok: true, text: "hello world" };
const obj = reactive(data);
function effect(fn) {
  const effectFn = () => {
    activeFn = effectFn;
    cleanup(effectFn);
    fn();
  };
  effectFn.deps = [];
  effectFn();
}
function cleanup(effectFn) {
  // 从副作用函数关联的依赖集合中删除副作用函数,从而断开关联
  for (const deps of effectFn.deps) {
    deps.delete(effectFn);
  }
  // 重置effectFn.deps
  effectFn.deps.length = 0;
}
function effect0() {
  console.log("%cindex.js line:83 obj.text", "color: #007acc;", obj.text);
}
effect(effect0);
obj.text = "hello vue";

产生的问题:代码运行发生栈溢出

具体问题代码:

obj.text = "hello vue";
// 触发trigger函数
function trigger(target, key) {
  ...
  // 调用包装的副作用函数
  effects.forEach((fn) => { // 1.effects
    fn();
  });
}
// 上面的fn
const effectFn = () => {
  activeFn = effectFn;
  // 把副作用函数从依赖集合中删除
  cleanup(effectFn);
  // 执行副作用函数,重新收集依赖
  fn();
};
function cleanup(effectFn) {
  // 从副作用函数关联的依赖集合中删除副作用函数,从而断开关联
  for (const deps of effectFn.deps) { // 此处的deps是上面的 1.effects
    // deps删除effectFn
    // effects中的副作用函数减少
    deps.delete(effectFn);
  }
  // 重置effectFn.deps
  effectFn.deps.length = 0;
}
function track(target, key) {
	...
  // 此处的deps是上面的 1.effects
  // effects添加副作用函数
  deps.add(activeFn);
  // 收集effectFn的依赖集合
  activeFn.deps.push(deps);
}

所以: 在遍历的过程中,每个回合删除元素,增加元素,导致遍历无法结束,导致栈溢出。

问题简单用代码展示如下:

const set = new Set([1])
set.forEach(item => {
  set.delete(1)
  set.add(1)
  console.log('遍历中')
})

如何解决此种情况下的栈溢出?

将遍历effects变成遍历effects的拷贝的值,不修改到efftcts就可以了

function trigger(target, key) {
  const depMap = bucket.get(target);
  if (!depMap) return;
  const effects = depMap.get(key);
  if (!effects) return;
  const effectsToRun = new Set(effects)
  effectsToRun.forEach((fn) => {
    fn();
  });
}

嵌套的effect与effect栈

effect嵌套的场景?

在Vue中,Vue的渲染函数就是在一个effect中执行的

主要的场景是:组件嵌套组件。

如果不支持effect嵌套,产生的后果

初始化

function effect(fn) {
  const effectFn = () => {
    activeFn = effectFn;
    activeFn.fnName = fn.name;
    console.log("fnName", activeFn.fnName);
    cleanup(effectFn);
    fn();
  };
  effectFn.deps = [];
  effectFn();
}
effect(function effect1() {
  console.log("effect1");
  effect(function effect2() {
    console.log("effect2", obj.text);
  });
  console.log("effect1", obj.ok);
});
// fnName effect1
// effect1
// fnName effect2
// effect2 hello world
// effect1 true

obj.ok = false;

// fnName effect2
// effect2 hello world

原因:

支持嵌套

const effectStack = [];
function effect(fn) {
  const effectFn = () => {
    activeFn = effectFn;
    cleanup(effectFn);
    // 把当前执行的函数压入栈中
    effectStack.push(effectFn);
    fn();
    // 函数执行完毕,弹出
    effectStack.pop();
    // activeFn赋值为还未执行完的副作用函数
    activeFn = effectStack[effectStack.length - 1];
  };
  effectFn.deps = [];
  effectFn();
}

避免无限递归循环

产生无限递归循环的代码:

const data = {foo : 1}
const obj = reactive(data)
effect(()=> obj.foo++)

原因分析:

() => {
  obj.foo = obj.foo + 1
}

obj.foo在读取自身之后又设置自身

解决循环

function trigger(target, key) {
  const depMap = bucket.get(target);
  if (!depMap) return;
  const effects = depMap.get(key);
  if (!effects) return;
  const effectsToRun = new Set();
  effects.forEach((fn) => {
    if (fn !== activeFn) {
      // 当触发的fn与当前执行的副作用函数不同时
      // 将fn添加到effectsToRun
      effectsToRun.add(fn);
    }
  });
  effectsToRun.forEach((fn) => {
    fn();
  });
}

完整代码

// 响应式数据的基本实现
let activeFn = undefined;
const bucket = new WeakMap();
// 副作用函数调用栈
const effectStack = [];
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key);
      return target[key];
    },
    // 在set操作中,赋值,然后调用effect函数
    set(target, key, value) {
      target[key] = value;
      trigger(target, key);
      return true;
    },
  });
}
// 收集effectFn的依赖集合
function track(target, key) {
  if (!activeFn) return target[key];
  let depMap = bucket.get(target);
  if (!depMap) {
    depMap = new Map();
    bucket.set(target, depMap);
  }
  let deps = depMap.get(key);
  if (!deps) {
    deps = new Set();
    depMap.set(key, deps);
  }
  deps.add(activeFn);
  // 收集effectFn的依赖集合
  activeFn.deps.push(deps);
}
function trigger(target, key) {
  const depMap = bucket.get(target);
  if (!depMap) return;
  const effects = depMap.get(key);
  if (!effects) return;
  const effectsToRun = new Set();
  effects.forEach((fn) => {
    if (fn !== activeFn) {
      // 当触发的fn与当前执行的副作用函数不同时
      // 将fn添加到effectsToRun
      effectsToRun.add(fn);
    }
  });
  effectsToRun.forEach((fn) => {
    if (fn.options.scheduler) {
      fn.options.scheduler(fn);
    } else {
      fn();
    }
  });
}
const data = { ok: true, text: "hello world" };
const obj = reactive(data);
function effect(fn) {
  const effectFn = () => {
    activeFn = effectFn;
    cleanup(effectFn);
    // 把当前执行的函数压入栈中
    effectStack.push(effectFn);
    fn();
    // 函数执行完毕,弹出
    effectStack.pop();
    // activeFn赋值为还未执行完的副作用函数
    activeFn = effectStack[effectStack.length - 1];
  };
  effectFn.deps = [];
  effectFn();
}
function cleanup(effectFn) {
  // 从副作用函数关联的依赖集合中删除副作用函数,从而断开关联
  for (const deps of effectFn.deps) {
    deps.delete(effectFn);
  }
  // 重置effectFn.deps
  effectFn.deps.length = 0;
}
function effect0() {
  console.log("%cindex.js line:83 obj.text", "color: #007acc;", obj.text);
}
effect(effect0);
obj.text = "hello vue";

加载全部内容

相关教程
猜你喜欢
用户评论