五、$set

Vue

# 动态添加一个响应式属性$set

# 准备html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Vue.js 01 component example</title>
  </head>
  <body>
    <div id="app">
      {{ obj.title }}
      <hr>
      {{ obj.name }}
      <hr>
      {{ arr }}
    </div>

    <script src="../../dist/vue.js"></script>
    <script>
      const vm = new Vue({
        el: '#app',
        data: {
          obj: {
            title: 'Hello Vue!'
          },
          arr: [1, 2, 3]
        }
      })
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

image

# 控制台设置值

如果要给obj.name赋值,是无法更新视图的

image

一般用 Vue.set 或者 vm.$set 方法,常用的是vm.$set,因为我们在项目中不太容易获取Vue的构造函数.

vm.$set(vm.obj, 'name', 'xm)

这个时候视图上出现了xm,说明添加的这个name是响应式数据.

image

如果修改数组的元素,之前使用splice方法可以,使用$set也可以

// 将第一个元素设置为100
vm.$set(vm.arr, 0, 100)
1
2

image

# vm.$set

  • 是Vue.set的别名.
  • 添加一个响应式的属性
  • 必须要在响应式对象上添加
  • 不能是Vue实例或者Vue的跟数据对象

# 源码解析

  • Vue.set()
    • global-api/index.js
    • 里面的set方法是在observer/index.js中定义(与响应式相关)
  • vm.$set()
    • instance/index.js
    • 里面的stateMixin函数在instance/state.js
    • 里面的Vue.prototype.$set也是在observer/index.js中定义,与静态方法set是一个方法
export function set (target: Array<any> | Object, key: any, val: any): any {
  // 判断传入的目标对象是否是undefined或者是原始值,不允许给undefined或者原始值添加响应式属性,会发送警告
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  // 判断target对象是否是数组,并且key是合法的索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 比较key和数组的length属性谁大,然后赋值给length属性,调用$set的时候数组可能会超过length属性
    target.length = Math.max(target.length, key)
    // 调用splice方法进行替换,这个不是数组的原生方法,是之前修改过的splice方法
    target.splice(key, 1, val)
    return val
  }
  // 处理对象属性
  // 判断处理的属性在对象中已经存在,并且这个属性不是Object原型上的成员,就直接赋值,不需要进行响应式处理
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  // 获取target的__ob__属性,判断其是不是vm或者是$data,是的话抛出警告
  // $data的话ob.vmCount是1
  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
  }
  // 判断ob对象是否存在,如果不存在说明target不是响应式对象,如果target不是响应式对象,那么传入的属性也不必做响应式处理,直接赋值返回
  if (!ob) {
    target[key] = val
    return val
  }

  // 如果ob存在,就把属性设置成响应式属性,ob.value即target
  defineReactive(ob.value, key, val)
  // 还要发送通知
  // 可以这么做是我们在收集依赖的时候,给每一个子对象都创建了childObj,并且给childObj的dep也收集了依赖
  // 因为那个收集了依赖,所以这里可以发送通知
  ob.dep.notify()
  // 最后将值返回
  return val
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
更新时间: 2022-01-13 21:13