effect的基本实现

export let activeEffect = undefined;// 当前正在执行的effectclass ReactiveEffect {active = true;deps = []; // 收集effect中使用到的属性parent = undefined;constructor(public fn) { }run() {if (!this.active) { // 不是激活状态return this.fn();}try {this.parent = activeEffect; // 当前的effect就是他的父亲activeEffect = this; // 设置成正在激活的是当前effectreturn this.fn();} finally {activeEffect = this.parent; // 执行完毕后还原activeEffectthis.parent = undefined;}}
}
export function effect(fn, options?) {const _effect = new ReactiveEffect(fn); // 创建响应式effect_effect.run(); // 让响应式effect默认执行
}

依赖收集

get(target, key, receiver) {if (key === ReactiveFlags.IS_REACTIVE) {return true;}const res = Reflect.get(target, key, receiver);track(target, 'get', key);  // 依赖收集return res;
}
const targetMap = new WeakMap(); // 记录依赖关系
export function track(target, type, key) {if (activeEffect) {let depsMap = targetMap.get(target); // {对象:map}if (!depsMap) {targetMap.set(target, (depsMap = new Map()))}let dep = depsMap.get(key);if (!dep) {depsMap.set(key, (dep = new Set())) // {对象:{ 属性 :[ dep, dep ]}}}let shouldTrack = !dep.has(activeEffect)if (shouldTrack) {dep.add(activeEffect);activeEffect.deps.push(dep); // 让effect记住dep,这样后续可以用于清理}}
}

将属性和对应的effect维护成映射关系,后续属性变化可以触发对应的effect函数重新run

触发更新

set(target, key, value, receiver) {// 等会赋值的时候可以重新触发effect执行let oldValue = target[key]const result = Reflect.set(target, key, value, receiver);if (oldValue !== value) {trigger(target, 'set', key, value, oldValue)}return result;
}
export function trigger(target, type, key?, newValue?, oldValue?) {const depsMap = targetMap.get(target); // 获取对应的映射表if (!depsMap) {return}const effects = depsMap.get(key);effects && effects.forEach(effect => {if (effect !== activeEffect) effect.run(); // 防止循环})
}

分支切换与cleanup

在渲染时我们要避免副作用函数产生的遗留

const state = reactive({ flag: true, name: 'jw', age: 30 })
effect(() => { // 副作用函数 (effect执行渲染了页面)console.log('render')document.body.innerHTML = state.flag ? state.name : state.age
});
setTimeout(() => {state.flag = false;setTimeout(() => {console.log('修改name,原则上不更新')state.name = 'zf'}, 1000);
}, 1000)
function cleanupEffect(effect) {const { deps } = effect; // 清理effectfor (let i = 0; i < deps.length; i++) {deps[i].delete(effect);}effect.deps.length = 0;
}
class ReactiveEffect {active = true;deps = []; // 收集effect中使用到的属性parent = undefined;constructor(public fn) { }run() {try {this.parent = activeEffect; // 当前的effect就是他的父亲activeEffect = this; // 设置成正在激活的是当前effect
+           cleanupEffect(this);return this.fn(); // 先清理在运行}}
}

这里要注意的是:触发时会进行清理操作(清理effect),在重新进行收集(收集effect)。在循环过程中会导致死循环。

let effect = () => {};
let s = new Set([effect])
s.forEach(item=>{s.delete(effect); s.add(effect)}); // 这样就导致死循环了

停止effect

export class ReactiveEffect {stop(){if(this.active){ cleanupEffect(this);this.active = false}}
}
export function effect(fn, options?) {const _effect = new ReactiveEffect(fn); _effect.run();const runner = _effect.run.bind(_effect);runner.effect = _effect;return runner; // 返回runner
}

调度执行

trigger触发时,我们可以自己决定副作用函数执行的时机、次数、及执行方式

export function effect(fn, options:any = {}) {const _effect = new ReactiveEffect(fn,options.scheduler); // 创建响应式effect// if(options){//     Object.assign(_effect,options); // 扩展属性// }_effect.run(); // 让响应式effect默认执行const runner = _effect.run.bind(_effect);runner.effect = _effect;return runner; // 返回runner
}export function trigger(target, type, key?, newValue?, oldValue?) {const depsMap = targetMap.get(target);if (!depsMap) {return}let effects = depsMap.get(key);if (effects) {effects = new Set(effects);for (const effect of effects) {if (effect !== activeEffect) { if(effect.scheduler){ // 如果有调度函数则执行调度函数effect.scheduler()}else{effect.run(); }}}}
}

深度代理

get(target, key, receiver) {if (key === ReactiveFlags.IS_REACTIVE) {return true;}// 等会谁来取值就做依赖收集const res = Reflect.get(target, key, receiver);track(target, 'get', key);if(isObject(res)){return reactive(res);}return res;
}

当取值时返回的值是对象,则返回这个对象的代理对象,从而实现深度代理

总结

  1. 为了实现响应式,我们使用了new Proxy
  2. effect默认数据变化要能更新,我们先将正在执行的effect作为全局变量,渲染(取值),然后在get方法中进行依赖收集
  3. 依赖收集的数据格式weakMap(对象:map(属性:set(effect))
  4. 用户数据发生变化,会通过对象属性来查找对应的effect集合,全部执行;
  5. 调度器的实现,创建effect时,把scheduler存在实例上,调用runner时,判断如果有调度器就调用调度器,否则执行runner

vue3响应式原理-effect相关推荐

  1. vue2和vue3响应式原理

    vue2响应式原理:核心使用Object.defineProperty给属性定义get和set方法 注意:对象的多次递归,针对数组需要重写数组方法 函数劫持:把函数内部进行重写同时继续调用老的方法,在 ...

  2. vue3响应式原理之Ref

    theme: fancy 一. Ref 用法 这是 ref 最基本的用法,返回来的count是一个响应式的代理值 const count = ref(0) 二. 实现 1. ref 函数 我们调用的r ...

  3. vue2的响应式原理学“废”了吗?继续观摩vue3响应式原理Proxy

    一文了解Vue3的响应式原理 一.

  4. Vue3 响应式原理

    如何实现响应式 作为一个高阶的概述,我们需要做到以下几点: 当一个值被读取时进行追踪 当某个值改变时进行检测 重新运行代码来读取原始值 Vue如何知道哪些代码在执行 为了能够在数值变化时,随时运行我们 ...

  5. vue3响应式原理-reflect

    proxy负责对某个数据进行增删改查的监听,不过vue3底层不是直接对target进行如下的简单操作.而是利用es6的window.reflect 利用reflect取一个对象的属性 利用reflec ...

  6. 手写简单vue3响应式原理(reactive ref toRef toRefs)

    reactive ref toRef toRefs // 判断对象是否是对象 const isObject = val => val !== null && typeof val ...

  7. Vue3的响应式原理解析

    Vue3的响应式原理解析 Vue2响应式原理回顾 // 1.对象响应化:遍历每个key,定义getter.setter // 2.数组响应化:覆盖数组原型方法,额外增加通知逻辑 const origi ...

  8. 【Vue3中的响应式原理】

    Vue3响应式原理 在Vue2的响应式中,存在着新增属性,删除属性以及直接通过下标修改数组,但页面不会自动更新的问题.但是在Vue3中,这些问题都得以解决. Vue2中的响应式原理 首先我们先看一下V ...

  9. Day 05- Vue3 Vue2响应式原理

    Vue2的响应式 核心:通过 Object.defineProtytype() 对对象的已有属性值的读取和修改进行劫持: 数据劫持  --> 给对象扩展属性 -->  属性设置 实现原理: ...

  10. 面试官:说说Vue响应式原理

    前言: 经常有面试官会问"你能说说vue的响应式原理吗?很多不明就里的人会说是v-model,其实面试官想问的是vue能实现响应式使用的是JS中的什么API,而且v-model这个属于双向数 ...

最新文章

  1. tar 和gzip 的区别
  2. Oracle快速复制表
  3. python写音乐播放器_python 模拟(简易)音乐播放器
  4. ubuntu下sublime如何一次只打開一個文件
  5. ElementUI 组件库 md-loader 的解析和优化
  6. 给Hangfire的webjob增加callback和动态判断返回结果功能设计
  7. C++实现有向图最短路径-Dijkstra单源最短路径算法
  8. 第三方应用商店仍为用户获取APP主渠道 细分市场或成新增长点
  9. c语言标准函数库怎么建立教程,C语言入门教程-创建一个函数库
  10. Slickflow.NET 开源工作流引擎高级开发(四) -- 硬核编码:代码式快速构建流程图...
  11. Struts2中过滤器和拦截器的区别
  12. kindle导出电子书pc_在PC版Kindle上阅读Mobi电子书
  13. msf(美少妇)练习
  14. 【安全知识分享】重磅|雨季安全生产教育.pptx(附下载)
  15. 戴尔(Dell)笔记本电脑开机后插上耳机没反应怎么办
  16. input文本框与图片的对齐
  17. 2022mathorcup数学建模大数据竞赛B题完整成品来啦!
  18. CCNA实验三十八 ZFW(区域防火墙)
  19. postek二次开发_各类标签一机打尽博思得C168条码打印机评测
  20. 【redis】redis各稳定版本特性(更新到6.0版本)

热门文章

  1. 【双足轮机器人】Ascento技术详解--(1)摘要和引言(2)系统描述【翻译】
  2. 解决Unable to resolve dependency for ‘:app@debug/compileClasspath’: Could not resolve com.android.supp
  3. 品质担当,有效提升办公生产力,永艺XY人体工学椅开箱实测
  4. badboy设置中文_badboy基本操作
  5. 感谢爱测未来,零基础的我的实习期是这么过来的
  6. 在那个春暖花开的季节 今天微微的小雨 伴着轻轻的晚风我们一起来编写 员工考勤信息管理...
  7. 恶搞-Mac 让电脑说话
  8. 文件无法删除 你需要计算机管理员 提供的权限才能对此文件进行更改解决办法
  9. c语言怎么做查询系统,c语言编辑查询系统,可实现增删改查
  10. Js实战之方块跟随鼠标移动