影评周公子 2026-02-02 19:00 采纳率: 98.8%
浏览 6
已采纳

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` 相关调用。
  • 写回答

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 = valdelete obj.keyin 操作符等均被自动捕获,所有嵌套层级默认响应式
    • set API 被移除非疏忽,而是范式演进的必然——它已失去存在语义基础。

    三、架构层: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 创建的对象生效;不可用于普通对象或 ref
    ref().value基础类型/单值/复杂对象const data = ref({}); data.value.newKey = 'val';若原 ref 指向普通对象,仍需确保其内部可响应(推荐用 ref(reactive({}))

    五、迁移层:自动化检测与渐进式清理策略

    建议在 CI 流程中集成以下检查:

    1. ESLint 插件 @vue/vue3-essential 启用 no-vue2-lifecycleno-vue2-refs 规则;
    2. 正则扫描:grep -r "\$set\|Vue\.set\|import.*set.*from.*'vue'" src/ --include="*.js" --include="*.ts" --include="*.vue"
    3. 编写 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.set and Vue.delete have 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”,而应审视“该对象是否由 reactiveref 正确创建?”、“当前作用域是否处于响应式上下文(如 setup()effect)?”、“是否误将非响应式对象混入响应式链路?”。这种契约意识比记忆 API 更本质,也更契合 Composition API 的设计哲学。

    ```
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

问题事件

  • 已采纳回答 2月3日
  • 创建了问题 2月2日