Vue3响应式原理设计和实现

news2024/11/26 18:24:57

Vue3响应式原理设计和实现

  • 响应式
    • 什么是响应式
    • 手动响应式
    • proxy代理对象
    • 响应式系统
      • 一个属性注册一个副作用函数
      • 一个属性注册多个副作用函数
      • 多个属性注册不同的副作用函数
      • 多个数据不同属性注册不同的副作用函数

响应式

什么是响应式

响应式是一个过程,这个过程存在两个参与者:一方触发,另一方响应。所谓响应式的两个参与者:

  • 触发者:数据
  • 响应者:引用该数据的函数(也叫副作用函数)

手动响应式

当数据改变时,引用该数据的函数响应数据的改变,重新执行。举一个最简单的手动响应式过程的例子:

<div id="app"></div>
const obj = {name: 'Jane'}

const effect = () => {
  app.innerHTML = obj.name
}

effect()

setTimeout(() => {
  obj.name = 'little Jane'
  // 手动执行副作用函数
  effect()
}, 1000)

其中,data 就是触发者数据,effect() 就是响应者,也叫副作用函数。

proxy代理对象

new Proxy 传入一个源对象,返回一个新对象(即代理对象),后续对代理对象的操作,都可以自定义访问过程。

const obj = {name: 'Jane'}
const proxy = new Proxy(obj, {
  get(target, key) {
    // 当访问代理对象的属性时执行 get 函数
    return target[key]
  },
  set(target, key, value) {
    // 当设置代理对象的属性时执行 set 函数
    target[key] = value
    return true
  }
})

其中,proxy 就是代理对象, target 是源对象,所以访问代理对象属性时其实返回的还是源对象内容,同样,对代理对象的属性操作其实最终还是操作的是源对象的属性。通过访问代理对象,可以自定义访问过程

响应式系统

一个属性注册一个副作用函数

通过 proxy代理对象,可以通过 proxy 原理实现一个 reactive 函数,用来将普通对象转化为 proxy 代理对象,并结合响应式系统:

const isObject = value => {
  return typeof value === 'object' && value !== null
}
const reactive = obj => {
  if(!isObject(obj)) return
  return new Proxy(obj, {
    get(target, key) {
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      effect() // 当设置 state.name 时,重新执行副作用函数
      return true
    }
  })
}

const state = reactive({name: 'Jane'})
const effect = () => {
  app.innerHTML = state.name
}
effect()
setTimeout(() => {
  state.name = 'little Jane'
}, 1000)

数据的改变,触发关联的副作用函数重新执行,通过 Proxy 代理源数据,在 Proxy 的自定义 set 操作中,重新执行副作用函数。

一个属性注册多个副作用函数

上面的响应式系统是一对一的数据和副作用函数关系。但是,理论上当属性改变时,属性关联的每一个副作用函数的都应该重新执行,这里 state.name 只被 effect 这一个函数引用了,但是如果有多个函数都引用了 state.name 的话,那这多个副作用函数都需要被重新执行。同时,为了避免副作用函数的重复,这里使用 set 类型来存放副作用函数集合:

const bucket = new Set() // 副作用函数桶,用来存放所有的副作用函数
const isObject = value => {
  return typeof value === 'object' && value !== null
}
const reactive = obj => {
  if(!isObject(obj)) return
  return new Proxy(obj, {
    get(target, key) {
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      bucket.forEach(fn => fn())
      return true
    }
  })
}

const state = reactive({name: 'Jane'})

const effect = () => {
  console.log('effect 执行')
}
bucket.add(effect)

const effect1 = () => {
  console.log('effect1 执行')
}
bucket.add(effect1)

// 触发 set,在 set 可以自定义执行所有副作用函数
setTimeout(() => {
  state.name = 'little Jane'
}, 1000)

多个属性注册不同的副作用函数

state 有多个属性时(例如 nameage 两个属性),不同的属性发生变化时应该执行对应的副作用函数,而不是执行所有数据的所有副作用函数。需要修改副作用函数桶的结构:
在这里插入图片描述

const bucket = new Map() // 副作用函数桶,用来存放所有的副作用函数
let activeEffect = null
const isObject = value => {
  return typeof value === 'object' && value !== null
}

// 收集依赖
const track = (target, key) => {
  if(!activeEffect) return
  let depSet = bucket.get(key)
  if(!depSet) {
    depSet = new Set()
    bucket.set(key, depSet)
  }
  depSet.add(activeEffect)
}

// 触发副作用函数
const trigger = (target, key) => {
  let depSet = bucket.get(key)
  if(depSet) {
    depSet.forEach(fn => fn())
  }
}

const reactive = obj => {
  if(!isObject(obj)) return
  return new Proxy(obj, {
    get(target, key) {
      // get 操作时收集依赖
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      // set 操作时,触发副作用函数执行
      trigger(target, key)
      return true
    }
  })
}

const effect = fn => {
  if(typeof fn !== 'function') return
  activeEffect = fn
  fn()
  activeEffect = null
}

const state = reactive({name: 'Jane', age: 25})

effect(() => {
  console.log('get执行', state.name)
})
effect(() => {
  console.log('get执行', state.age)
})

console.log(bucket)

在这里插入图片描述

多个数据不同属性注册不同的副作用函数

有多个 state 数据时,它们可能有不同属性名,但是也可能会有相同属性名
在这里插入图片描述

let activeEffect = null
let bucket = new WeakMap() // [state -> Map[name -> Set(fn, fn), age -> Set(fn, fn)], state1 -> Map]

const isObject = value => {
  return typeof value === 'object' && value !== null
}

// 收集依赖
const track = (target, key) => {
  if(!activeEffect) return
  let depMap = bucket.get(target)
  if(!depMap) {
    depMap = new Map()
    bucket.set(target, depMap)
  }
  let depSet = depMap.get(key)
  if(!depSet) {
    depSet = new Set()
    depMap.set(key, depSet)
  }
  depSet.add(activeEffect)
}

// 触发依赖
const trigger = (target, key) => {
  let depMap = bucket.get(target)
  if(!depMap) return
  let depSet = depMap.get(key)
  if(depSet) {
    depSet.forEach(fn => fn())
  }
}

const reactive = obj => {
  if(!isObject(obj)) return
  return new Proxy(obj, {
    get(target, key) {
      // get 操作时收集依赖
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      // set 操作时,触发副作用函数执行
      trigger(target, key)
      return true
    }
  })
}

const effect = fn => {
  if(typeof fn !== 'function') return
  activeEffect = fn
  fn()
  activeEffect = null
}

const state1 = reactive({name: 'Jane', age: 25})
const state2 = reactive({name: 'Qu', age: 27})

effect(() => {
  console.log('我在拿 state1.name...', state1.name)
})
effect(() => {
  console.log('我在拿 state1.age...', state1.age)
})
effect(() => {
  console.log('我在拿 state2.name......', state2.name)
})
effect(() => {
  console.log('我又拿 state2.name......', state2.name)
})

console.log(bucket)

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/92411.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MaixPy】:K210识别简例(简单二维码检测和双二维码检测)

实物图 俩二维码识别实物图 前言 这段时间接触了一下基于MaixPy的开发K210的摄像头设备,的确很有趣,运行速度很快,编程难度不大。很适合咱们视觉开发的同学们学习,以下是我玩设备的一些感悟,如有不妥之处,希望大家雅正,也希望能帮助初学者了解和学习,也可加bulidupup(…

java毕业设计——基于java+Socket+sqlserver的网络通讯系统设计与实现(毕业论文+程序源码)——网络通讯系统

基于javaSocketsqlserver的网络通讯系统设计与实现&#xff08;毕业论文程序源码&#xff09; 大家好&#xff0c;今天给大家介绍基于javaSocketsqlserver的网络通讯系统设计与实现&#xff0c;文章末尾附有本毕业设计的论文和源码下载地址哦。 文章目录&#xff1a; 基于jav…

LwIP源码分析(3):内存堆和内存池代码详解

文章目录1 内存堆1.1 mem_init1.2 mem_malloc1.3 mem_free2 内存池2.1 memp_init2.2 memp_malloc2.3 memp_free3 内存管理宏定义在嵌入式系统中&#xff0c;内存池有助于快速有效地分配内存。LwIP提供了两个灵活的方式来管理和组织内存池的大小&#xff1a;内存堆和内存池。当然…

电脑维修记录

记于2022年12月15日 今天把电脑修好了&#xff0c;总结这次经验&#xff1a; &#xff08;1&#xff09;无知者无畏&#xff0c;对任何事情都要抱有探索的精神&#xff0c;最遗憾的事情不是做错了&#xff0c;而是想做没去做 &#xff08;2&#xff09;将每次走错路的经历都…

Orcale数据表去重创建联合主键

分享一下最近遇到的一个问题&#xff0c;我们从一个数据表中将数据表中的数据同步到另一个数据库的表中&#xff0c;由于要同步的数据表中没有建主键&#xff0c;所以数据同步后发现同步的数据比原始数据表中的数据要多&#xff0c;有不少重复的数据。因此需要对数据表进行去重…

事业编招聘:市委社会工委北京市民政局事业单位公开招聘

市委社会工委市民政局所属事业单位 根据《北京市事业单位公开招聘工作人员实施办法》&#xff08;京人社专技发﹝2010﹞102号&#xff09;等文件精神&#xff0c;北京市委社会工委北京市民政局所属21家事业单位面向社会及应届毕业生公开招聘事业单位工作人员88名。现将具体情况…

【SpringBoot 2.x】定时任务 之- @Scheduled注解

一、概述 Scheduled注解是Spring Boot提供的用于定时任务控制的注解&#xff0c;主要用于控制任务在某个指定时间执行&#xff0c;或者每隔一段时间执行。注意需要 启动类加EnableScheduling实现类加Component方法上加ScheduledScheduled主要有以下几种配置执行时间的方式&…

Neural Discrete Representation Learning (VQ-VAE) 简介

目录VQ-VAE参考VQ-VAE VAE是一种生成模型。 Vector QuantisedVariational AutoEncoder (VQ-VAE)是VAE的变种&#xff0c;其隐含变量是离散的。离散的隐含变量对于自然语言&#xff0c;推理都比较有帮助。著名的DALL-E就使用了类似VQ-VAE的离散隐含变量来从文本生成图像&#x…

【Python】同一网络下,手机和电脑进行socket通信

同一网络下&#xff0c;手机和电脑进行socket通信 最近在学python网络编程&#xff0c;发现socket可以进行跨主机的进程通信&#xff0c;于是尝试用电脑作为服务端&#xff0c;手机作为客户端&#xff0c;来进行socket通信。 电脑端准备 1.电脑开启热点&#xff08;非必须&a…

[附源码]Python计算机毕业设计SSM基于vue的图书管理系统2022(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

java递归实现多级Map集合合并(结合实际场景)

合并Map集合 合并Map集合有很多方法&#xff0c;例如Map自生提供的putAll()方法。但是这种方法只能合并一层的集合&#xff0c;如果是多层的呢&#xff1f; 场景 现在有一个yml配置文件&#xff0c;由于项目部署在多台服务器上&#xff0c;而且每台服务器上的配置有些许差异…

Ajax(四)

1.模板引擎 1.1 模板引擎的基本概念 1.2 什么是模板引擎 不需要再用字符串拼接 1. 3模板引擎的好处 1.4 art-template模板引擎 art-template 是一个简约、超快的模板引擎。 中文官网首页为 http://aui.github.io/art-template/zh-cn/index.html 1.4.1 art-template模板引擎的…

为什么Python现在这么火?

Python可以说是目前最火的网红编程语言&#xff0c;虽然它在近几年在逐渐流行起来&#xff0c;但其实它已经发展了近三十年。那么&#xff0c;为什么Python现在这么火呢&#xff1f;一方面人工智能和大数据的崛起带红了Python&#xff0c;另一方面无论是软件开发者还是非编程工…

抗肿瘤的靶向药物——艾美捷西妥昔单抗Cetuximab说明

近年来恶性肿瘤极大的治疗进展是靶向新药的开发与使用。表皮生长因子受体EGFR是一种具有酪氨酸激酶活性的跨膜受体&#xff0c;受表皮生长因子EGF和转化生长因子-α(TGF-α)的刺激。多种肿瘤细胞株过度表达EGFR&#xff0c;包括25%&#xff5e;80%的结直肠癌CRC细胞。 西妥昔单…

RSS Can:使用 Golang Rod 解析浏览器中动态渲染的内容:(四)

第四篇文章&#xff0c;来聊聊 Golang 生态中如何“遥控”浏览器&#xff0c;更简单、可靠的使用基于 CDP &#xff08;Chrome DevTools Protocol&#xff09;协议的浏览器作为容器&#xff0c;获取诸如微博、B 站 这类动态渲染内容信息&#xff0c;将它们转换为 RSS 订阅源。 …

【C语言进阶】不会处理字符串?一万三千五百字包会保姆级教程

目录 &#x1f618;前言&#x1f618;&#xff1a; 一、字符串处理函数介绍&#x1f92f;&#xff1a; 1.strlen 函数&#x1f94e;&#xff1a; 2.strcpy 函数⚾&#xff1a; 3.strcat 函数&#x1f3c0;&#xff1a; 4.strcmp 函数&#x1f3c8;&#xff1a; 5.strncpy 函数…

WSL_03 WSL2 从C盘迁移到D盘

文章目录1 动机1 查看虚拟机状态&#xff0c;并关闭要迁移的虚拟机2 迁移WSL22.1 出现的问题&#xff1a;已存在具有提供的名称的分发(已解决)3 设置启动时的默认用户&#xff0c;没有设置默认为root参考1 动机 WSL2默认安装在C盘中&#xff0c;win R运行中使用%localappdata…

python科学计算 之 Numpy库的使用详解

目录 一&#xff1a;Numpy简介 二&#xff1a;ndarray的创建 三&#xff1a;ndarray的属性 四&#xff1a;切片 索引 五&#xff1a;数组的轴(二维数组) 六&#xff1a;二维数组 切片索引 七&#xff1a;高级索引 八&#xff1a;Numpy广播 九&#xff1a;ufunc函数 算…

使用pypy来提升你的python项目性能

一、PyPy介绍 PyPy是用Python实现的Python解释器的动态编译器&#xff0c;是Armin Rigo开发的产品&#xff0c;能够提升我们python项目的运行速度。PyPy 是利用即时编译的 Python 的替代实现。背后的原理是 PyPy 开始时就像一个解释器&#xff0c;直接从源文件运行我们的 Pyth…

Revit二次开发小技巧(十五)构件的最小矩形外轮廓

前言&#xff1a;我们会经常遇到需要计算一个构件的最小外轮廓&#xff0c;一般直接取BoundingBox只有最大和最小值坐标&#xff0c;也是基于x-y坐标系下的。往往不是最小的矩形&#xff0c;所以分享下面的算法来计算最小的外轮廓&#xff0c;条件为法向量是指向Z轴的&#xff…