技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTVUE

详解 Vue.set 和 this.$set 的用法与使用场景

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/98-详解Vue set和this $set用法与使用场景
本文目录10 个章节
  1. 一、为什么 Vue 2 需要 Vue.set
  2. 二、Vue 2 数组的变化
  3. 三、Vue 2 对象的变化
  4. 四、Vue.set 的签名和使用限制
  5. 五、Vue 2 中 this.$set 大概做了什么
  6. 六、Vue 3 为什么不再需要 Vue.set
  7. 七、Vue 2 与 Vue 3 对照
  8. 八、常见误解
  9. 总结
  10. 参考资料

详解 Vue.set 和 this.$set 的用法与使用场景

原文主要介绍 Vue 2 中对象、数组的响应式检测限制,以及 Vue.set/this.$set 的用法。本文保留 Vue 2 的历史 API 和示例,同时明确 Vue 3 已移除这些 API,并解释 Proxy 为什么不再需要它们。

一、为什么 Vue 2 需要 Vue.set

Vue 2 的响应式系统在初始化时遍历对象已有属性,并使用 Object.defineProperty 转换为 getter/setter。这个机制无法自动发现初始化之后新增或删除的对象属性,也无法拦截数组下标赋值和 length 修改。

const state = {
  user: {
    name: '小明',
  },
  items: ['a', 'b', 'c'],
}

// Vue 2 中这些操作不会按预期触发响应式更新
state.user.age = 18
state.items[1] = 'x'
state.items.length = 1

注意:这不是说 Vue 2 完全不能检测对象或数组变化。已有属性的普通赋值,以及被 Vue 改写过的数组变异方法,仍然可以触发更新。

二、Vue 2 数组的变化

1. 使用数组变异方法

Vue 2 对下面七个方法进行了包装:

push()
pop()
shift()
unshift()
splice()
sort()
reverse()
this.items.push({ id: 4 })
this.items.splice(1, 1, 'new value')
this.items.sort(compareItems)

这些方法会通知数组 observer,并且对新增的对象元素继续做响应式观测。

Vue 2 包装数组变异方法的示意图(已本地化)

2. 用 splice 替代数组下标赋值

// 不推荐:Vue 2 无法可靠检测
this.items[index] = newValue

// 推荐
this.items.splice(index, 1, newValue)

3. 使用 Vue.setthis.$set

Vue.set(this.items, index, newValue)

// 组件实例方法
this.$set(this.items, index, newValue)

两者最终解决的是同一类问题:对数组下标设置值时,转而调用 splice,让 Vue 2 的数组 observer 能够收到通知。

4. 修改数组长度

// Vue 2 中不是响应式的
this.items.length = newLength

// 删除从 index 开始的剩余元素
this.items.splice(index)

// 截取后替换整个数组
this.items = this.items.slice(0, newLength)

使用 Vue.set 并不能把 length 赋值变成响应式;应使用 splice 或替换数组引用。

5. 替换整个数组

非变异方法 filterconcatslice 会返回新数组,赋值给响应式属性即可:

this.items = this.items.filter(item => item !== 'obsolete')
this.items = this.items.concat(newItem)

Vue 2 的虚拟 DOM 通常可以复用新旧数组中相同的对象和 DOM 节点,因此“替换数组”不等于一定重建整个列表。

三、Vue 2 对象的变化

1. 初始化时声明字段

如果字段以后会使用,最稳妥的方式是在 data 中提前声明:

new Vue({
  data() {
    return {
      user: {
        name: '小明',
        age: null,
      },
    }
  },
})

已有响应式属性直接修改是可以检测的:

this.user.name = '小红'
this.user.age = 20

2. 使用 Vue.set

给嵌套对象添加新属性:

Vue.set(this.user, 'gender', '女')
this.$set(this.user, 'gender', '女')

完整示例:

<div id="app">
  <ul>
    <li v-for="(value, name) in object" :key="name">
      {{ name }}:{{ value }}
    </li>
  </ul>
</div>

<script>
const vm = new Vue({
  el: '#app',
  data: {
    object: {
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10',
    },
  },
})

vm.$set(vm.object, 'test', 'newthing')
</script>

原文的示例中把多行代码拼在同一行,并且缺少属性逗号;上面按照 Vue 2 可运行的写法整理。

3. 整体替换对象

也可以创建一个新对象并替换引用:

this.object = {
  ...this.object,
  test: 'newthing',
}

不要期待下面的 Object.assign 对新增字段自动产生 Vue 2 响应式通知:

// 不推荐作为 Vue 2 新增属性的解决方案
Object.assign(this.object, {
  test: 'newthing',
})

如果使用 Object.assign,应将结果替换回响应式属性:

this.object = Object.assign({}, this.object, {
  test: 'newthing',
})

四、Vue.set 的签名和使用限制

Vue.set(target, propertyNameOrIndex, value)
this.$set(target, propertyNameOrIndex, value)

对象

Vue.set(vm.someObject, 'b', 2)
this.$set(this.someObject, 'b', 2)

数组

Vue.set(vm.items, 1, 'x')
this.$set(this.items, 1, 'x')

不能用来添加根级 data 属性

Vue 2 不允许在实例创建后随意添加新的根级响应式属性:

const vm = new Vue({
  data: {
    a: 1,
  },
})

// 不是可靠的响应式根属性添加方式
vm.b = 2
Vue.set(vm, 'b', 2)

正确做法是在 data 中预先声明:

const vm = new Vue({
  data: {
    a: 1,
    b: null,
  },
})

或者替换已存在的嵌套对象:

vm.options = {
  ...vm.options,
  b: 2,
}

原文中的 app.data 是错误访问;Vue 实例上没有一个可以用来替代根 data 的 app.data 对象。根 data 中的字段会代理到 vm,但新增根属性仍应在初始化时声明。

五、Vue 2 中 this.$set 大概做了什么

下面是概念化实现,省略 observer 类型判断、警告和边界处理:

function set(target, key, value) {
  if (Array.isArray(target) && Number.isInteger(Number(key))) {
    target.length = Math.max(target.length, Number(key) + 1)
    target.splice(Number(key), 1, value)
    return value
  }

  if (key in target && !(target._isVue || target.__ob__)) {
    target[key] = value
    return value
  }

  const ob = target.__ob__
  if (!ob) {
    target[key] = value
    return value
  }

  defineReactive(ob.value, key, value)
  ob.dep.notify()
  return value
}

Vue 2 的真实实现会:

  1. 对数组下标调用 splice
  2. 如果属性本来存在,直接赋值;
  3. 对已观测的嵌套对象调用 defineReactive
  4. 通过对象 observer 的 dep 通知相关 watcher;
  5. 拒绝或警告对 Vue 实例根对象添加属性。

Vue.set 不是“让任意普通对象永久变成 Vue 响应式对象”的函数。目标对象最好已经属于 Vue 2 的响应式数据树。

六、Vue 3 为什么不再需要 Vue.set

Vue 3 的 reactive 使用 Proxy,可以拦截对象属性新增、删除、数组下标和相关集合操作:

<script setup>
import { reactive } from 'vue'

const state = reactive({
  user: {},
  items: ['a', 'b', 'c'],
})

state.user.age = 18
state.items[1] = 'x'
state.items.length = 1
delete state.user.age
</script>

这些操作都可以被 Vue 3 的 Proxy handler 追踪。Vue 3 已移除:

Vue.set
Vue.delete
this.$set
this.$delete

迁移后的写法就是直接修改 reactive 对象:

state.user.age = 18
state.items[index] = newValue
delete state.user.age

如果使用 ref 保存数组或对象,要修改 .value

import { ref } from 'vue'

const items = ref(['a', 'b', 'c'])
items.value[1] = 'x'

const user = ref({})
user.value.age = 18

对于 shallowReactiveshallowRefmarkRaw 或外部状态库,还要根据其浅层/非响应式语义判断是否需要替换引用。

七、Vue 2 与 Vue 3 对照

场景Vue 2Vue 3
已有对象属性赋值直接赋值直接赋值
新增嵌套对象属性Vue.set/this.$set 或替换对象直接赋值
删除对象属性Vue.delete/this.$delete 或替换对象delete state.key
数组下标赋值Vue.setsplice直接赋值
修改 lengthsplice 或替换数组Proxy 可追踪,但仍需确认业务语义
根级新属性初始化时声明setup/reactive 状态中直接声明或添加
APIVue.setvm.$set已移除

Vue 3 不需要 $set,并不意味着可以不考虑状态设计。推荐在初始化时声明主要字段,使用 TypeScript 类型、明确的状态结构和不可变替换来提高可读性。

Vue 2 响应式数组方法说明(已本地化)

八、常见误解

误解 1:Vue 2 所有数组直接赋值都不响应

错误。pushsplice 等七个变异方法会触发更新;直接设置下标和 length 才是经典限制。

误解 2:只要使用 Vue.set 就能添加根属性

错误。Vue 2 实例的根级属性需要在初始化时声明;Vue.set 主要用于已观测对象的嵌套属性和数组下标。

误解 3:Vue 3 完全不需要关注新增字段

Vue 3 可以追踪 Proxy 对象的新增属性,但如果对象不是 reactive、被 markRaw、来自 shallowRef,或被错误解构成普通值,仍可能没有你期望的响应式行为。

误解 4:替换数组一定会重建所有 DOM

Vue 的 renderer 会根据 vnode 和 key 尽量复用节点。数据引用被替换与 DOM 节点全部重建不是一回事。

总结

  • Vue 2 的限制来自 Object.defineProperty 无法拦截新增属性、删除属性、数组下标和 length;
  • Vue 2 对象新增属性用 Vue.set/this.$set,数组下标可用 $setsplice
  • Vue 2 根级 data 属性应在初始化时声明,不能靠 $set 事后补救;
  • Object.assign 直接给 Vue 2 对象增加新字段不可靠,推荐替换对象引用;
  • Vue 3 使用 Proxy,通常直接新增、删除和修改数组下标即可,不再提供 Vue.set/this.$set
  • Vue 3 的 shallow/非响应式 API 和错误解构仍然可能影响响应式行为。

参考资料

原文作者:Dax1。原文关于 Vue 2 数组变异方法、对象新增属性、Vue.setthis.$set 和使用场景的内容予以保留;错误的根属性示例、拼接损坏的代码、app.data 访问、Vue 3 仍需 $set 等表述已修正,并补充 Vue 3 Proxy 方案。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS