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

目录
  • 分支切换与cleanup
    • 了解分支切换
    • 分支切换导致的问题
    • 如何清除掉副作用函数的无效关联关系?
    • 疑问:为什么对传入的副作用函数进行一层包裹?
    • 完整代码
    • 产生的问题:代码运行发生栈溢出
    • 如何解决此种情况下的栈溢出?
  • 嵌套的effect与effect栈
    • effect嵌套的场景?
    • 初始化
    • 原因:
    • 支持嵌套
  • 避免无限递归循环
    • 产生无限递归循环的代码:
    • 原因分析:
    • 解决循环
  • 完整代码

分支切换与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为无效,应该清除掉,如下图展示

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

  • 每次副作用函数执行前,可以先把它从所有与之关联的依赖集合中删除,然后清空依赖集合的收集,
  • 当副作用函数执行,所有会重新建立关联。(副作用函数中,会重新执行响应式数据的get操作,从而进行收集依赖)

步骤:

  • 副作用函数收集与自身关联的依赖集合
  • effect注册副作用函数中为effectFn增添一个属性deps,用来存储依赖集合,

    track函数中,进行依赖集合的收集

  • 将副作用函数从与之关联的所有依赖集合中移除,
  • effect注册副作用函数中,触发副作用函数前,清除副作用函数的依赖集合

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

  • 为了对副作用函数进行更多操作,
  • 为副作用函数增加deps属性,作为收集依赖集合的容器
  • 清除副作用函数的依赖集合
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);
}
  • 当设置响应式对象的值时,触发trigger函数,遍历依赖集合,
  • 遍历的过程中,每个回合,被包裹的副作用函数执行,
  • cleanup,把副作用函数从依赖集合中删除

    触发副作用函数

    副作用函数执行触发响应式数据的get操作,重新收集依赖函数

  • 继续遍历

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

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

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

原因:

  • 执行effect(effect1)代码
  • 执行effectFn
  • effectFn函数中,activeFn包裹的副作用函数为effect1
  • 执行effect1
  • 触发了effect(effect2),此时effect1还没有被收集
  • 执行effectFn
  • effectFn函数中,activeFn包裹的副作用函数为effect2
  • 执行effect2
  • effect2被收集,effect2执行完成
  • 继续执行effect1,此时activeFn包裹的副作用函数仍为effect2
  • 所以此时收集的副作用函数又为effect2
  • 执行obj.ok = false;
  • 遍历对应的依赖集合,触发effect2

支持嵌套

  • 需要把正在执行,且没有执行完的被包裹的副作用函数存入栈中
  • 当最上面的被包裹的副作用函数执行完,弹出
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在读取自身之后又设置自身

  • 读取obj.foo会触发track
  • track收集依赖后,然后继续执行上面的赋值操作
  • 设置obj.foo会触发trigger
  • 然后遍历依赖集合,再次触发obj.foo的读取
  • 循环

解决循环

  • 设置和读取是在一个副作用函数中进行的,都是activeEffect
  • 如果trigger触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行
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";

以上就是vue开发设计分支切换与cleanup实例详解的详细内容,更多关于vue开发设计分支切换cleanup的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue2 响应式系统之分支切换

    目录 场景 observer(data) new Watcher(updateComponent) data.ok = false data.text = "hello, liang" 问题 去重 重置 测试 总结 场景 我们考虑一下下边的代码会输出什么. import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { text: &quo

  • vue.js树形组件之删除双击增加分支实例代码

    html代码: <script type="text/x-template" id="item-template"> <li> <div :class="{bold: isFolder}" @click="toggle"> {{model.name}} <span v-if="isFolder">[{{open ? '-' : '+'}}]</span&

  • Vue中的table表单切换实现效果

    目录 Vue表单切换实现效果 首先给两个链接定义 一个num Vue table切换组件 Vue表单切换实现效果 点击第一个链接 出现以下数据 点击第二个链接 ,我没有写后台所以没有数据, 可以自己写方法去获取数据复制给v-model 绑定的数组 首先给两个链接定义 一个num 点击第一个按钮时 设置num等于1 , 这样在table列表处定义 v-show ="num==1 ",当等于1 时 显示第一个table 当等于num 等于 2时 等于第二个table 这样就能实现 tabl

  • Vue transx组件切换动画库示例详解

    目录 来个介绍 安装 使用 支持参数 支持事件 支持API 支持的动画类型 说明 来个介绍 先奉上组件库的名称:transx github地址:github.com/tnfe/transx npm参考: www.npmjs.com/package/tra… 示例地址:codesanbox 安装 npm install transx or yarn add transx 使用 <!-- 包裹动画元素 --> <trans-x :time="time" :delay=&q

  • 使用vue-antd动态切换主题

    目录 vue-antd动态切换主题 安装依赖 Vue3.0 + Antd,修改antd主题色,配置全局css vue-antd动态切换主题 安装依赖 1 webpack-theme-color-replacer: yarn add webpack-theme-color-replacer@1.3.22 2 less: yarn add less@2.7.2 3 less-loader: yarn add less-loader@7.0.2 在vue.config.js中加入配置 const cr

  • vue 鼠标移入移出(hover)切换显示图片问题

    目录 鼠标移入移出(hover)切换显示图片 css实现 js实现 vue鼠标移入移出事件注意事项 发生冒泡事件 一.解决方法 二.定义 三.两对鼠标事件的区别 鼠标移入移出(hover)切换显示图片 css实现 代码: <div @click="exitConnect()" class="exit_hover">        <img class="exit1 mr10" :src="exit" styl

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

    目录 分支切换与cleanup 了解分支切换 分支切换导致的问题 如何清除掉副作用函数的无效关联关系? 疑问:为什么对传入的副作用函数进行一层包裹? 完整代码 产生的问题:代码运行发生栈溢出 如何解决此种情况下的栈溢出? 嵌套的effect与effect栈 effect嵌套的场景? 初始化 原因: 支持嵌套 避免无限递归循环 产生无限递归循环的代码: 原因分析: 解决循环 完整代码 分支切换与cleanup 了解分支切换 代码示例如下 const data = { ok: true, text:

  • C++设计与实现ORM系统实例详解

    目录 介绍 依赖关系 设计思路 项目进度 数据库通用接口 实例构造 智能查询方式设计 单元测试 运行方法 介绍 我们通用的ORM,基本模式都是想要脱离数据库的,几乎都在编程语言层面建立模型,由程序去与数据库打交道.虽然脱离了数据库的具体操作,但我们要建立各种模型文档,用代码去写表之间的关系等等操作,让初学者一时如坠云雾.我的想法是,将关系数据库拥有的完善设计工具之优势,来实现数据设计以提供结构信息,让json对象自动映射成为标准的SQL查询语句.只要我们理解了标准的SQL语言,我们就能够完成数据

  • vue组件中的样式属性scoped实例详解

    Scoped CSS Scoped CSS规范是Web组件产生不污染其他组件,也不被其他组件污染的CSS规范. vue组件中的style标签标有scoped属性时表明style里的css样式只适用于当前组件元素 它是通过使用PostCSS来改变以下内容实现的: <style scoped> .example { color: red; } </style> <template> <div class="example">hi</di

  • IOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解 在实际的开发需求时,有时候我们需要对某些对象进行打包,最后拼接到参数中 例如,我们把所有的参数字典打包为一个 字符串拼接到参数中 思路:利用系统系统JSON序列化类即可,NSData作为中间桥梁 //1.字典转换为字符串(JSON格式),利用 NSData作为桥梁; NSDictionary *dic = @{@"name":@"Lisi",@"sex":@"m",@"tel&qu

  • Swift 开发之懒加载的实例详解

    Swift 开发之懒加载的实例详解 /// A display link that keeps calling the `updateFrame` method on every screen refresh. private lazy var displayLink: CADisplayLink = { self.isDisplayLinkInitialized = true let displayLink = CADisplayLink(target: TargetProxy(target:

  • JSP开发中Apache-HTTPClient 用户验证的实例详解

    JSP开发中Apache-HTTPClient 用户验证的实例详解 前言: 在微服务框架之外的系统中,我们经常会遇到使用httpClient进行接口调用的问题,除了进行白名单的设置,很多时候我们需要在接口调用的时候需要身份认证.翻了一下官方文档,解决方法很多,但是都不太符合实际业务场景,这里提供一种简单粗暴的解决方法. 解决方法:利用请求头,将验证信息保存起来. 实现代码: public class HttpClientUtils { protected static final Logger

  • Android开发高仿课程表的布局实例详解

    先说下这个demo,这是一个模仿课程表的布局文件,虽然我是个菜鸟,但我还是想留给学习的人一些例子,先看下效果 然后再来看一下我们学校的app 布局分析 先上一张划分好了的布局图 首先整个页面放在一个LinearLayout布局下面,分为上面和下面两个部分,下面一个是显示课程表的详细信息 1:这个没什么好讲的,就是直接一个LinearLayout布局,然后将控件一个TextView用来显示年份,一个View用来当作竖线,一个Spinner用来显示选择周数 2:这个是显示星期几的部件,是我自定义的V

  • Vue自定义全局Toast和Loading的实例详解

    如果我们的Vue项目中没有用到任何UI框架的话,为了更好的用户体验,肯定会用到loading和toast.那么我们就自定义这两个组件吧. 1.Toast组件 首先,在common下新建global文件夹,存放我们的toast.vue和toast.js两个文件(当然文件的具体位置你可以自行安排). (1). toast.vue <template lang="html"> <div v-if="isShowToast" class="toa

  • Vue中遍历数组的新方法实例详解

    1.foreach foreach循环对不能使用return来停止循环 search(keyword){ var newList = [] this.urls.forEach(item =>{ if(item.name.indexOf(keyword) != -1){ newList.push(item) } }) return newList } 2.filter item对象就是遍历数组中的一个元素,includes是es6中的新方法,在search方法中直接返回新数组 search(key

  • Vue触发隐藏input file的方法实例详解

    1.使用input透明覆盖法 将input的z-index设置为1以上的数字并覆盖到需点击的内容上,将input的样式opacity设置为0(即为透明度为0),这样通过绑定在input上的change事件触发     ----推荐 <p class="uploadImg"> <input type="file" @change="picUpload($event)" accept="image/*" />

随机推荐