0


完全掌握 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

构造函数自身上的

set

vm

实例上的

$set

是相同的函数。**

解决了以下问题:

1.新增对象的属性

2.删除对象的属性

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

在这里插入图片描述

对应源码

完整源码
exportfunctionset(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 inObject.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. 接受参数类型分别为  数组/对象; 任意 ; 任意exportfunctionset(target: Array<any>| Object,key: any,val: any): any {// 2. 判断第一个参数 不为 undefined null string number symbol booleanif(
    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 inObject.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
}/* 工具函数 */functionisPrimitive(value){return(typeof value ==='string'||typeof value ==='number'||// $flow-disable-linetypeof value ==='symbol'||typeof value ==='boolean')}// explicitness and function inlining.functionisUndef(v){return v ===undefined|| v ===null}// 是否是有效的数组索引functionisValidArrayIndex(val){const n =parseFloat(String(val))return n >=0&& Math.floor(n)=== n &&isFinite(val)}

在这里插入图片描述

小结:

主要的处理顺序:

  1. 处理数组(使用 劫持过的数组 splice 方法);
  2. 处理对象上自带的属性;
  3. 收集依赖,手动触发。
// 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.
 * 如果需要,删除属性并触发更改。
 */exportfunctiondel(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;}// 如果 属性不是自身的属性,直接 returnif(!hasOwn(target, key)){return;}// 删除对应的keydelete target[key];// 不是响应式的不做处理(这个地方可以理解为,浅层监听的 watch,有些深层的属性不需要watch,就会走这个情况)if(!ob){return;}// 手动触发 !! 有作者在想,直接在代码中 `.__ob__`  手动通知不就ok了? 虽然可以但是不建议这样做、
  ob.dep.notify();}

end

上述的演示,源码查看的是

 [email protected]

。 vue3中由于响应式实现原理发生了变化,所以不需要 $set 了,所以不做探究。


本文转载自: https://blog.csdn.net/wswq2505655377/article/details/128927996
版权归原作者 upward_tomato 所有, 如有侵权,请联系我们删除。

“完全掌握 Vue 中的 $set 方法”的评论:

还没有评论