iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Vue3中响应式的特殊处理方法是什么
  • 584
分享到

Vue3中响应式的特殊处理方法是什么

2023-07-06 00:07:53 584人浏览 八月长安
摘要

本篇内容介绍了“vue3中响应式的特殊处理方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Vue2 vs vue3两个响应式更新的核

本篇内容介绍了“vue3中响应式的特殊处理方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

Vue2 vs vue3

两个响应式更新的核心区别在于Object.definePropertyProxy 两个api 问题,经过这两个 api 能解决主要的响应式问题。对于一些情况需要特殊处理

vue2 中不能实现的响应式

  • arr.length

  • arr[0] = newVal

  • obj[newKey] = value

  • delete obj.key

对于这些情况 vue2 中通过增加Vue.$set和重写数组方法来实现。然而对于 vue3 中,因为 proxy 是代理整个对象,所以它天生支持一个Object.defineProperty 不能支持的特性,比如他能侦听到添加新属性,而 Object.defineProperty因为代理的是每一个 key 所以它对于新增的属性并不能知道。诸如此类,下面列出一些vue3 中不同的响应式处理。

新增属性的更新

proxy 虽然能够监听到新属性的添加,但新增的属性并没有经过像已有属性那样的 getter 收集依赖,也就是并不能触发更新。所以我们的目的变成如何收集响应

首先,我们先看下 vue3 中是如何处理 for...in.. 循环的,可以知道的是循环的内部使用了Reflect.ownKeys(obj) 来获取只属于对象自身拥有的键。所以对于 for..in 循环的拦截就可以清楚了

const obj = {foo: 1}const ITERATE_KEY = symbol()const p = new Proxy(obj, {   track(target, ITERATE_KEY)   return Reflect.ownKeys(target)})

这里,我们用了一个symbol 的数据作为 收集依赖的key 因为这个是我们在遍历中的拦截操作没有与具体的 key 关联,而是一个整体性的拦截。在触发响应时,只要触发这个 symbol 收集的 effect 就可以

trigger(target, isArray(target) ? 'length' : ITERATE_KEY) // 数组的情况追踪 length

这里会发生影响遍历对象长度时,会引ITERATE_KEY 相关的副作用函数执行

effect(() => {    for(let i in obj) {        console.log(i)    }})

副作用函数执行后,类比我们执行了渲染函数。 然后回到我们的新增属性,

p.newKey = 1

因为新增属性,会对for.. in .. 循环产生影响,所以我们需要把与 ITERATE_KEY 相关的副作用函数拿出来重新执行,看看源码中这块的处理

首先这里是 setter 的处理

function createSetter(shallow = false) {  return function set(    target: object,    key: string | symbol,    value: unknown,    receiver: object  ): boolean {    let oldValue = (target as any)[key]    ...    // 这里是表明是否有 key 也就是判断是否是新增元素    const hadKey =      isArray(target) && isIntegerKey(key)        ? Number(key) < target.length        : hasOwn(target, key)    const result = Reflect.set(target, key, value, receiver)    // don't trigger if target is something up in the prototype chain of original    if (target === toRaw(receiver)) {      if (!hadKey) {         // 这里表明是新增元素时,走的 trigger 逻辑        trigger(target, TriggerOpTypes.ADD, key, value)      } else if (hasChanged(value, oldValue)) {        trigger(target, TriggerOpTypes.SET, key, value, oldValue)      }    }    return result  }}

然后是 具体的 trigger,拿到对应的标识去更新effect

export function trigger(  target: object,  type: TriggerOpTypes,  key?: unknown,  newValue?: unknown,  oldValue?: unknown,  oldTarget?: Map<unknown, unknown> | Set<unknown>) {  const depsMap = targetMap.get(target)  if (!depsMap) {    // never been tracked    return  }    ...    // also run for iteration key on ADD | DELETE | Map.SET    switch (type) {    // 这种情况就是我们刚刚确定的 trigger 的执行      case TriggerOpTypes.ADD:        if (!isArray(target)) {        // 拿到收集的 ITERATE_KEY 的依赖          deps.push(depsMap.get(ITERATE_KEY))          if (isMap(target)) {            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))          }        } else if (isIntegerKey(key)) {          // new index added to array -> length changes          deps.push(depsMap.get('length'))        }        break         ...  }    for (const dep of deps) {      if (dep) {        effects.push(...dep)      }    }    ...    triggerEffects(createDep(effects))    ...  }}function triggerEffect(  effect: ReactiveEffect,  debuggerEventExtraInfo?: DebuggerEventExtraInfo) {  if (effect !== activeEffect || effect.allowRecurse) {    if (__DEV__ && effect.onTrigger) {      effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))    }    if (effect.scheduler) {      effect.scheduler()    } else {     // 执行所有的 effect 函数      effect.run()    }  }}

总结

在遍历对象或数组时,用一个唯一标识symbol 收集依赖

  • 其实如果在模板中直接使用 obj 会伴随着一个 JSON.stringify 的过程,也会伴随着收集依赖。

  • 我们在 js 代码里如果没有用到遍历对象,单独对一个对象新增是不会触发更新的,因为没有收集的过程。

在设置新值时,获取收集的symbol对应的副作用函数更新

遍历数组方法的处理

在使用数组时,会伴随着 this 的问题导致代理对象拿不到属性的问题,比如

const obj = {}const arr = reactive([obj])console.log(arr.includes(obj) // false

之所以会出现这样的问题,是因为 includes 内部的this 指向的是代理对象 arr, 并且在因为比较去获取元素时拿到的也是代理对象,所以拿原始对象去找肯定找不到。所以,我们需要去修改inlcudes的行为,

new Proxy(obj, {    get() {        if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {          return Reflect.get(arrayInstrumentations, key, receiver)        }    }})   const arrayInstrumentations =  createArrayInstrumentations()// 处理集中数组遍历方法中的问题。function createArrayInstrumentations() {  const instrumentations: Record<string, Function> = {}  // instrument identity-sensitive Array methods to account for possible reactive  // values   ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {      const arr = toRaw(this) as any      for (let i = 0, l = this.length; i < l; i++) {        track(arr, TrackOpTypes.GET, i + '')      }      // we run the method using the original args first (which may be reactive)      // 先使用原始参数,可能是原始对象,也可能是代理对象。      const res = arr[key](...args)      if (res === -1 || res === false) {        // if that didn't work, run it again using raw values.        // 如果没有找到,就拿到原始参数去比较,脱完响应式的数据。        return arr[key](...args.map(toRaw))       } else {        return res      }    }  })

数组的变更方法

对于可能会更改原数组长度的数组方法,push, pop, shift, unshift, splice 也需要进行处理,否则,就会陷入无限递归当中,考虑下面的场景

cosnt arr = reactive([])effect(() => {    arr.push(1)})effect(() => {    arr.push(1)})

这两个的执行过程如下:

  • 首先,第一个副作用函数执行,然后数组中添加1, 并且这个过程会给影响数组的 length, 所以会与 length 会被 track , 建立响应式联系。

  • 然后第二个副作用函数执行,执行 push 这时因为影响了 length,先track 建立响应式联系,然后会试图拿出length的副作用函数也就是第一个副作用函数执行,然而这时第二个副作用函数还未执行完成,就又开始执行第一个副作用函数了,

  • 第一个副作用函数再次执行,同样会读取length 并且设置 length,重复上面收集和更新的过程,又要把第二个副作用中收集的 length 执行

  • 如此循环往复。最终会栈溢出。

所以问题的关键就在于 length 的不断读取和设置。所以我们需要在读取到 length,避免它与副作用函数之间建立联系

;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {      // 禁止追踪变化,       pauseTracking()      const res = (toRaw(this) as any)[key].apply(this, args)      // 等函数执行完毕时再回复追踪。      resetTracking()      return res    }  })

“Vue3中响应式的特殊处理方法是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!

--结束END--

本文标题: Vue3中响应式的特殊处理方法是什么

本文链接: https://www.lsjlt.com/news/356801.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++ 生态系统中流行库和框架的贡献指南
    作为 c++++ 开发人员,通过遵循以下步骤即可为流行库和框架做出贡献:选择一个项目并熟悉其代码库。在 issue 跟踪器中寻找适合初学者的问题。创建一个新分支,实现修复并添加测试。提交...
    99+
    2024-05-15
    框架 c++ 流行库 git
  • C++ 生态系统中流行库和框架的社区支持情况
    c++++生态系统中流行库和框架的社区支持情况:boost:活跃的社区提供广泛的文档、教程和讨论区,确保持续的维护和更新。qt:庞大的社区提供丰富的文档、示例和论坛,积极参与开发和维护。...
    99+
    2024-05-15
    生态系统 社区支持 c++ overflow 标准库
  • c++中if elseif使用规则
    c++ 中 if-else if 语句的使用规则为:语法:if (条件1) { // 执行代码块 1} else if (条件 2) { // 执行代码块 2}// ...else ...
    99+
    2024-05-15
    c++
  • c++中的继承怎么写
    继承是一种允许类从现有类派生并访问其成员的强大机制。在 c++ 中,继承类型包括:单继承:一个子类从一个基类继承。多继承:一个子类从多个基类继承。层次继承:多个子类从同一个基类继承。多层...
    99+
    2024-05-15
    c++
  • c++中如何使用类和对象掌握目标
    在 c++ 中创建类和对象:使用 class 关键字定义类,包含数据成员和方法。使用对象名称和类名称创建对象。访问权限包括:公有、受保护和私有。数据成员是类的变量,每个对象拥有自己的副本...
    99+
    2024-05-15
    c++
  • c++中优先级是什么意思
    c++ 中的优先级规则:优先级高的操作符先执行,相同优先级的从左到右执行,括号可改变执行顺序。操作符优先级表包含从最高到最低的优先级列表,其中赋值运算符具有最低优先级。通过了解优先级,可...
    99+
    2024-05-15
    c++
  • c++中a+是什么意思
    c++ 中的 a+ 运算符表示自增运算符,用于将变量递增 1 并将结果存储在同一变量中。语法为 a++,用法包括循环和计数器。它可与后置递增运算符 ++a 交换使用,后者在表达式求值后递...
    99+
    2024-05-15
    c++
  • c++中a.b什么意思
    c++kquote>“a.b”表示对象“a”的成员“b”,用于访问对象成员,可用“对象名.成员名”的语法。它还可以用于访问嵌套成员,如“对象名.嵌套成员名.成员名”的语法。 c++...
    99+
    2024-05-15
    c++
  • C++ 并发编程库的优缺点
    c++++ 提供了多种并发编程库,满足不同场景下的需求。线程库 (std::thread) 易于使用但开销大;异步库 (std::async) 可异步执行任务,但 api 复杂;协程库 ...
    99+
    2024-05-15
    c++ 并发编程
  • 如何在 Golang 中备份数据库?
    在 golang 中备份数据库对于保护数据至关重要。可以使用标准库中的 database/sql 包,或第三方包如 github.com/go-sql-driver/mysql。具体步骤...
    99+
    2024-05-15
    golang 数据库备份 mysql git 标准库
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作