一文完全掌握Vue中的$set方法

 更新时间:2023年11月01日 10:49:53   作者:upward_tomato  
这篇文章主要给大家介绍了关于如何完全掌握Vue中$set方法的相关资料,vue中$set方法对数组和对象的处理本质上的一样的,对新增的值添加响应然后手动触发派发更新,需要的朋友可以参考下

start

今天在使用 $set 的时候,发现如果 被赋值的数据 层级较深会出现报错的情况。

一知半解,是我最讨厌的状态,今天就带着问题,再阅读一下对应的源码,了解问题的本质。

问题说明

简单说明一下我遇到的问题,明确探究问题的目标。

需求

我有一个空对象,我希望可以给它的属性的属性的属性赋值。

错误代码:

<template>
  <div>
    lazy_tomato

    <h2>{{ obj }}</h2>

    <button @click="handleChange">点击我给obj赋值</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      obj: {},
    }
  },
  methods: {
    handleChange() {
      this.obj.a = {
        b: {
          c: '爱吃番茄',
        },
      }

      console.log(JSON.stringify(this.obj))
      // 直接新增属性,不会触发 vue2本质的Object.defineProperty。所以数据更新了视图不更新
    },
  },
}
</script>

正确代码

<template>
  <div>
    lazy_tomato
    <h2>{{ obj }}</h2>
    <button @click="handleChange">点击我给obj赋值</button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      obj: {},
    }
  },
  methods: {
    handleChange() {
      // 错误代码二  typeError: Cannot read properties of undefined (reading '__ob__')
      // this.$set(this.obj.a, 'b.c', '爱吃番茄')
      // 正确代码
      this.$set(this.obj, 'a', { b: { c: '爱吃番茄' } })
      console.log(JSON.stringify(this.obj))
    },
  },
}
</script>

所以 $set 对这三个参数分别是如何处理的?如何避免我们错误使用?

官方文档

区分 Vue.set 和 vm.$set

Vue 构造函数自身上的 setvm 实例上的 $set 是相同的函数。

解决了以下问题:

1.新增对象的属性

2.删除对象的属性

3.通过数组索引修改数据

对应源码

完整源码

export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

分析源码

// 1. 接受参数类型分别为  数组/对象; 任意 ; 任意
export function set(target: Array<any> | Object, key: any, val: any): any {
  // 2. 判断第一个参数 不为 undefined null string number symbol boolean
  if (
    process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(
      `Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
    )
  }

  // 3. 如果是数组,而且第二个参数是有效索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 更新数组长度 有可能传入的索引大于现有索引
    target.length = Math.max(target.length, key)

    // 调用 splice
    target.splice(key, 1, val)

    // // 返回值是设置的值
    return val
  }

  // 4. 是该对象的属性 (且不是原型链上的属性)
  if (key in target && !(key in Object.prototype)) {
    // 直接赋值 (这里赋值可以触发 Object.defineProperty)
    target[key] = val

    // 返回值是设置的值
    return val
  }

  // 5. 获取 observe实例
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' &&
      warn(
        'Avoid adding reactive properties to a Vue instance or its root $data ' +
          'at runtime - declare it upfront in the data option.'
      )
    return val
  }

  // 6. 无observe实例,直接赋值,// 返回值是设置的值
  if (!ob) {
    target[key] = val
    return val
  }

  // 7. 收集依赖
  defineReactive(ob.value, key, val)

  // 8. 手动通知,触发视图更新
  ob.dep.notify()
  // // 返回值是设置的值
  return val
}

/* 工具函数 */
function isPrimitive(value) {
  return (
    typeof value === 'string' ||
    typeof value === 'number' ||
    // $flow-disable-line
    typeof value === 'symbol' ||
    typeof value === 'boolean'
  )
}

// explicitness and function inlining.
function isUndef(v) {
  return v === undefined || v === null
}

// 是否是有效的数组索引
function isValidArrayIndex(val) {
  const n = parseFloat(String(val))
  return n >= 0 && Math.floor(n) === n && isFinite(val)
}

小结:

主要的处理顺序:

  • 处理数组(使用 劫持过的数组 splice 方法);
  • 处理对象上自带的属性;
  • 收集依赖,手动触发。
// this.$set(this.obj.a, 'b.c', '爱吃番茄')
错误的原因,this.obj.a 本身是 undefined 所以直接被第一步就拦截了。

// this.obj.a={}
// this.$set(this.obj.a, 'b.c', '爱吃番茄')
也达不到效果,它会直接吧 b.c当做属性名初始化

思考:

虽然官方文档设定,第二个参数是数字和字符串,理论上可以传入其他类型的。第二个参数最好是单层级的属性值

扩展 :del 方法

/**
 * Delete a property and trigger change if necessary.
 * 如果需要,删除属性并触发更改。
 */
export function del(target: Array<any> | Object, key: any) {
  if (
    process.env.NODE_ENV !== "production" &&
    // 如果是 undefined 或 null; 或者是原始值 ---同Vue.$set
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(
      `Cannot delete reactive property on undefined, null, or primitive value: ${target}`
    );
  }

  // 数组,利用splice,直接改
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1);
    return;
  }

  // ---同Vue.$set 排除Vue实例 和 根对象
  const ob = (target: any).__ob__;
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== "production" &&
      warn(
        "Avoid deleting properties on a Vue instance or its root $data " +
          "- just set it to null."
      );
    return;
  }

  // 如果 属性不是自身的属性,直接 return
  if (!hasOwn(target, key)) {
    return;
  }

  // 删除对应的key
  delete target[key];

  // 不是响应式的不做处理(这个地方可以理解为,浅层监听的 watch,有些深层的属性不需要watch,就会走这个情况)
  if (!ob) {
    return;
  }

  // 手动触发 !! 有作者在想,直接在代码中 `.__ob__`  手动通知不就ok了? 虽然可以但是不建议这样做、
  ob.dep.notify();
}

end

上述的演示,源码查看的是 vue@2.6。 vue3中由于响应式实现原理发生了变化,所以不需要 $set 了,所以不做探究。

到此这篇关于完全掌握Vue中$set方法的文章就介绍到这了,更多相关Vue $set方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解决vue过滤器filters获取不到this对象的问题

    解决vue过滤器filters获取不到this对象的问题

    这篇文章主要介绍了解决vue过滤器filters获取不到this对象的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • elementplus card 悬浮菜单的实现

    elementplus card 悬浮菜单的实现

    本文主要介绍了elementplus card 悬浮菜单的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • vue+webpack dev本地调试全局样式引用失效的解决方案

    vue+webpack dev本地调试全局样式引用失效的解决方案

    今天小编就为大家分享一篇vue+webpack dev本地调试全局样式引用失效的解决方案,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-11-11
  • vue 通过下拉框组件学习vue中的父子通讯

    vue 通过下拉框组件学习vue中的父子通讯

    这篇文章主要介绍了vue 通过下拉框组件学习vue中的父子通讯的相关知识,文中涉及到了父组件,子组件的实现代码,需要的朋友可以参考下
    2017-12-12
  • 关于对keep-alive的理解,使用场景以及存在的问题解读

    关于对keep-alive的理解,使用场景以及存在的问题解读

    这篇文章主要介绍了关于对keep-alive的理解,使用场景以及存在的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • 你不知道的SpringBoot与Vue部署解决方案

    你不知道的SpringBoot与Vue部署解决方案

    这篇文章主要介绍了你不知道的SpringBoot与Vue部署解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • 如何用Vue3切换中英文显示举例说明

    如何用Vue3切换中英文显示举例说明

    这篇文章主要给大家介绍了关于如何用Vue3切换中英文显示的相关资料,在Vue3中使用vue-i18n进行国际化设置,包括安装、配置、在组件中使用$t方法获取翻译字符串,以及可选的动态加载语言包以提高性能,需要的朋友可以参考下
    2024-11-11
  • Vue编写多地区选择组件

    Vue编写多地区选择组件

    这篇文章主要为大家详细介绍了Vue编写一个挺靠谱的多地区选择组件的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • 在Vue中实现网页截图与截屏功能详解

    在Vue中实现网页截图与截屏功能详解

    在Web开发中,有时候需要对网页进行截图或截屏,Vue作为一个流行的JavaScript框架,提供了一些工具和库,可以方便地实现网页截图和截屏功能,本文将介绍如何在Vue中进行网页截图和截屏,需要的朋友可以参考下
    2023-06-06
  • vue实现登录页面的验证码以及验证过程解析(面向新手)

    vue实现登录页面的验证码以及验证过程解析(面向新手)

    这篇文章主要介绍了vue实现登录页面的验证码以及验证过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08

最新评论