Vue 3 中为何使用 `set()` 会报错“undefined is not a function”?
在 Vue 3 中直接调用 `set()`(如 `this.$set` 或全局 `Vue.set`)会报错 “undefined is not a function”,根本原因是:**Vue 3 已彻底移除 `Vue.set` / `vm.$set` 等响应式 API**。该方法仅存在于 Vue 2 中,用于向响应式对象动态添加属性并触发更新;而 Vue 3 基于 Proxy 重构响应式系统,**所有嵌套属性默认具备响应性**,不再需要手动 `set`。若代码中沿用 Vue 2 的写法(如 `this.$set(obj, 'key', val)` 或 `import { set } from 'vue'`),将因模块未导出或实例无该方法而抛出 `undefined is not a function`。正确替代方案是:使用 `reactive()` + 直接赋值(支持新增属性),或对 `ref` 使用 `.value` 赋值;若需深层响应式更新,应确保原对象本身由 `reactive()` 创建。迁移时务必清理所有 `Vue.set` 相关调用。
- 写回答
- 好问题 0 提建议
- 关注问题
- 邀请回答
-
1条回答 默认 最新
Jiangzhoujiao 2026-02-02 19:00关注```html一、现象层:错误表征与典型报错现场
在 Vue 3 项目中执行
this.$set(obj, 'name', 'Alice')或import { set } from 'vue'后调用,控制台明确抛出:“TypeError: undefined is not a function”。该错误高频出现在 Vue 2 → Vue 3 迁移初期、遗留组件复用、或开发者凭经验直写旧 API 的场景中。二、机制层:Vue 2 与 Vue 3 响应式范式的根本断裂
- Vue 2 依赖 Object.defineProperty:无法检测新增属性(如
obj.newKey = val),故需Vue.set/vm.$set显式劫持并触发依赖更新; - Vue 3 采用 Proxy + Reflect 全面重构:对整个对象代理拦截,
obj.newKey = val、delete obj.key、in操作符等均被自动捕获,所有嵌套层级默认响应式; setAPI 被移除非疏忽,而是范式演进的必然——它已失去存在语义基础。
三、架构层:Vue 3 响应式核心模块的导出契约
查看
node_modules/vue/dist/vue.runtime.esm-bundler.js或官方类型定义vue/types/reactivity.d.ts可确认:
✅ 导出reactive,ref,computed,readonly
❌ 无任何set,deleteProperty,$set相关导出项四、实践层:四大替代路径与适用边界
方案 适用对象类型 语法示例 注意事项 reactive()+ 直接赋值深度响应式对象 const state = reactive({}); state.count = 1; state.newField = 'ok';仅对 reactive创建的对象生效;不可用于普通对象或refref().value基础类型/单值/复杂对象 const data = ref({}); data.value.newKey = 'val';若原 ref 指向普通对象,仍需确保其内部可响应(推荐用 ref(reactive({})))五、迁移层:自动化检测与渐进式清理策略
建议在 CI 流程中集成以下检查:
- ESLint 插件
@vue/vue3-essential启用no-vue2-lifecycle和no-vue2-refs规则; - 正则扫描:
grep -r "\$set\|Vue\.set\|import.*set.*from.*'vue'" src/ --include="*.js" --include="*.ts" --include="*.vue"; - 编写 Jest 单元测试断言:对含
$set的组件实例化时捕获TypeError并标记待修复。
六、原理层:Proxy 如何天然支持动态属性响应?
const handler = { set(target, key, value) { // ✅ 拦截任意 key 赋值,包括不存在的 key Reflect.set(target, key, value); trigger(target, key); // 触发 effect 更新 return true; } }; const proxy = new Proxy({}, handler); proxy.newProp = 'hello'; // 自动响应!无需额外 API七、陷阱层:看似可行但实际失效的“伪替代”
⚠️ 下列写法不能保证响应性:
const obj = {}; const proxy = reactive(obj); proxy.newKey = 'val';—— ❌obj非 reactive 初始化,proxy 代理的是空对象,后续赋值不触发收集;const r = ref({}); r.value = { ...r.value, newKey: 'val' };—— ⚠️ 会丢失响应性(浅替换),应改用Object.assign(r.value, { newKey: 'val' })或直接点赋值。
八、演进层:Vue 官方迁移指南的权威印证
查阅 Vue 3 Migration Guide — Global API Removal 明确声明:
“Vue.setandVue.deletehave been removed. Use native JavaScript operations instead.”
并附代码对比图(见下方流程图):graph LR A[Vue 2] -->|需要显式调用| B[Vue.set(obj, key, val)] C[Vue 3] -->|直接赋值即响应| D[obj.key = val] B -.-> E[移除:API 不存在] D --> F[Proxy 拦截 set trap] F --> G[自动 trigger effect]九、工程层:构建可复用的迁移辅助工具函数
为平滑过渡,可封装兼容层(仅限临时过渡,上线前必须移除):
// utils/vue3-compat.ts export function $set(target: T, key: string | number | symbol, value: any): void { if ('__v_isReactive' in target && (target as any).__v_isReactive) { (target as any)[key] = value; // 直接赋值 } else { console.warn(`[$set] Target is not reactive:`, target); } } // 使用:$set(state, 'loading', true)十、认知层:从“API 调用思维”到“响应式契约思维”的跃迁
资深开发者需完成范式升级:不再追问“如何 set”,而应审视“该对象是否由
```reactive或ref正确创建?”、“当前作用域是否处于响应式上下文(如setup()或effect)?”、“是否误将非响应式对象混入响应式链路?”。这种契约意识比记忆 API 更本质,也更契合 Composition API 的设计哲学。本回答被题主选为最佳回答 , 对您是否有帮助呢?解决 无用评论 打赏 举报- Vue 2 依赖 Object.defineProperty:无法检测新增属性(如