Vue 2、Vue 3 、Vuex 3、Vuex 4 和 Pinia 响应式丢失场景及解决方案

Vue2与Vue3响应式机制对比及解决方案


Vue2使用Object.defineProperty实现响应式,存在多个响应式丢失场景:

  1. 直接添加/删除属性需使用Vue.set/Vue.delete
  2. 数组索引修改需使用Vue.set或splice
  3. 解构赋值会丢失响应式

Vue3基于Proxy的响应式系统更完善:

  1. 自动响应属性添加/删除
  2. 直接数组操作保持响应
  3. 仍需toRefs保持解构响应式

状态管理方案对比:

  • Vuex3/4需通过mutations修改状态
  • Pinia支持直接修改,自动响应式
  • 解构状态需使用storeToRefs

最佳实践:

  1. Vue2使用Vue.set处理动态属性
  2. Vue3优先使用reactive+toRefs
  3. Pinia直接修改状态,配合$patch批量更新

Vue 2 Vue 3 响应式丢失场景及解决方案

响应式丢失场景 Vue 2 (Object.defineProperty) Vue 3 (Proxy) 解决方案
1. 直接添加新属性 ❌ 丢失响应式 ✅ 自动响应 Vue 2: Vue.set(obj, key, value) Vue 3: 直接赋值 obj.newKey = value
2. 直接删除属性 ❌ 丢失响应式 ✅ 自动响应 Vue 2: Vue.delete(obj, key) Vue 3: delete obj.key
3. 通过索引修改数组 ❌ 丢失响应式 ✅ 自动响应 Vue 2: Vue.set(arr, index, value)arr.splice() Vue 3: 直接赋值 arr[index] = value
4. 修改数组长度 ❌ 丢失响应式 ✅ 自动响应 Vue 2: arr.splice(newLength) Vue 3: arr.length = newLength
5. 解构赋值 ❌ 丢失响应式 ❌ 丢失响应式 使用 toRefstoRef 保持响应式
6. 直接导出响应式对象 ⚠️ 部分丢失 ⚠️ 部分丢失 使用 reactive + toRefscomputed
7. 将响应式对象赋值给新变量 ❌ 丢失响应式 ❌ 丢失响应式 保持引用关系,或使用 toRefs/toRef
8. 函数参数传递 ❌ 丢失响应式 ❌ 丢失响应式 传递 ref 对象而非值,或使用 computed
9. 响应式对象中的嵌套对象 ⚠️ 需递归转换 ✅ 懒代理(自动响应) Vue 2: 预先定义或 Vue.set Vue 3: 自动处理
10. Map/Set 等集合类型 ❌ 不支持 ✅ 支持 Vue 3 可直接使用 reactive(new Map())
11. 类实例属性 ❌ 不支持 ✅ 支持 Vue 3 可直接代理类实例
12. 异步操作中赋值 ⚠️ 需注意 this 指向 ⚠️ 需注意 this 指向 使用箭头函数或保存 this 引用

详细说明与代码示例

1. 直接添加新属性

javascript 复制代码
// Vue 2
data() {
  return { user: { name: 'Alice' } }
},
methods: {
  addAge() {
    this.user.age = 25  // ❌ 非响应式
    this.$set(this.user, 'age', 25)  // ✅ 响应式
  }
}

// Vue 3
const state = reactive({ user: { name: 'Alice' } })
state.user.age = 25  // ✅ 自动响应式

2. 直接删除属性

javascript 复制代码
// Vue 2
delete this.user.age  // ❌ 非响应式
this.$delete(this.user, 'age')  // ✅ 响应式

// Vue 3
delete state.user.age  // ✅ 自动响应式

3-4. 数组操作

javascript 复制代码
// Vue 2
this.items[0] = newValue  // ❌ 非响应式
this.items.length = 0     // ❌ 非响应式
this.$set(this.items, 0, newValue)  // ✅
this.items.splice(0, 1, newValue)   // ✅
this.items.splice(0, this.items.length)  // ✅

// Vue 3
const items = reactive([1, 2, 3])
items[0] = 100     // ✅ 自动响应式
items.length = 0   // ✅ 自动响应式

5. 解构赋值(两者都丢失)

javascript 复制代码
// ❌ 错误示例
const state = reactive({ count: 0, name: 'Alice' })
let { count, name } = state  // 失去响应式
count++  // 不会触发更新

// ✅ 解决方案
import { toRefs } from 'vue'
const state = reactive({ count: 0, name: 'Alice' })
const { count, name } = toRefs(state)  // 保持响应式
count.value++  // 触发更新

// 单个属性使用 toRef
const count = toRef(state, 'count')
count.value++  // 触发更新

6. 直接导出响应式对象

javascript 复制代码
// Vue 2 (composables)
export default function useCounter() {
  let count = ref(0)  // Vue 2.7+ 支持
  const increment = () => count.value++
  return { count, increment }  // ✅ 保持响应式
}

// Vue 3
export default function useCounter() {
  const state = reactive({ count: 0 })
  const increment = () => state.count++
  return { ...toRefs(state), increment }  // ✅ 使用 toRefs 保持响应式
}

7. 将响应式对象赋值给新变量

javascript 复制代码
// ❌ 错误示例
const state = reactive({ count: 0 })
let newState = state  // newState 和 state 引用同一个对象
newState.count = 1    // ✅ 仍然响应式

let { count } = state  // ❌ 失去响应式

// ✅ 正确做法
const count = toRef(state, 'count')  // 保持响应式引用

8. 函数参数传递

javascript 复制代码
// ❌ 错误示例
const state = reactive({ count: 0 })
function updateCount(count) {
  count = 10  // 失去响应式
}
updateCount(state.count)

// ✅ 解决方案
function updateCount(countRef) {
  countRef.value = 10  // 传递 ref 对象
}
updateCount(toRef(state, 'count'))

// 或使用 computed
const doubleCount = computed(() => state.count * 2)

9. 嵌套对象响应式

javascript 复制代码
// Vue 2
data() {
  return {
    user: {
      profile: {
        name: 'Alice'  // 需要预先定义才能响应式
      }
    }
  }
},
methods: {
  addNested() {
    // ❌ 动态添加深层属性可能丢失响应式
    this.user.profile.age = 25
    this.$set(this.user.profile, 'age', 25)  // ✅ 需要 $set
  }
}

// Vue 3
const state = reactive({
  user: {
    profile: {
      name: 'Alice'
    }
  }
})
// ✅ 自动响应式,无需预先定义
state.user.profile.age = 25
state.user.address = { city: 'Beijing' }  // 也自动响应式

10. Map/Set 集合类型

javascript 复制代码
// Vue 2
data() {
  return {
    map: new Map()  // ❌ Map 本身不是响应式
  }
}

// Vue 3
const map = reactive(new Map())  // ✅ Map 操作也是响应式的
map.set('key', 'value')
console.log(map.get('key'))  // 触发响应式更新

11. 类实例属性

javascript 复制代码
// Vue 2
class User {
  constructor(name) {
    this.name = name
  }
}
data() {
  return {
    user: new User('Alice')  // ❌ 类实例属性非响应式
  }
}

// Vue 3
class User {
  constructor(name) {
    this.name = name
  }
}
const user = reactive(new User('Alice'))  // ✅ 属性变为响应式
user.name = 'Bob'  // 触发更新

12. 异步操作中的 this 指向

javascript 复制代码
// Vue 2
methods: {
  async fetchData() {
    setTimeout(function() {
      this.data = result  // ❌ this 指向错误
    }, 1000)
    
    setTimeout(() => {
      this.data = result  // ✅ 箭头函数保持 this
    }, 1000)
  }
}

// Vue 3
setup() {
  const data = ref(null)
  const fetchData = async () => {
    setTimeout(() => {
      data.value = result  // ✅ 箭头函数保持引用
    }, 1000)
  }
  return { data, fetchData }
}

特殊场景对比

场景 Vue 2 Vue 3 说明
响应式对象赋值 this.obj = newObj state.obj = newObj Vue 3 可以整体替换
条件添加属性 this.$set 必须 直接赋值 ✅ Vue 3 更简洁
对象冻结 Object.freeze() 破坏响应式 同样破坏 两者都需要注意
异步数据更新 需用 this.$nextTick 需用 nextTick 机制相同
v-for 索引更新 不推荐 支持但需注意 Vue 3 支持更好

核心原则与最佳实践

Vue 2 记忆口诀

新增删除用 set/delete,数组方法用 splice,解构导出需谨慎

Vue 3 记忆口诀

reactive 配 toRefs,ref 解构用 toRef,直接赋值全响应

通用解决方案

  1. 保持引用:避免破坏响应式对象的引用链

  2. 使用工具函数toRefstoRefcomputed

  3. 提前定义结构:在 reactive 中预先定义所有需要的属性

  4. 使用 ref 包装:基础类型使用 ref,对象使用 reactive

代码检查清单

javascript 复制代码
// ✅ 安全操作
const state = reactive({ count: 0, list: [] })
state.count++                    // 修改属性
state.newProp = 'value'          // 添加属性
delete state.count               // 删除属性
state.list[0] = newValue         // 修改数组项
state.list.length = 0            // 修改数组长度

// ⚠️ 需要小心
const { count } = toRefs(state)  // 解构用 toRefs
const double = computed(() => state.count * 2)  // 派生状态用 computed

// ❌ 危险操作
let { count } = state             // 直接解构
let newState = { ...state }       // 展开运算符

这种对比可以帮助开发者在实际开发中快速识别响应式丢失的场景,并选择合适的解决方案。


Vuex3,Vuex4和Pinia响应式丢失场景及解决方案


Vuex 3、Vuex 4 和 Pinia 响应式丢失场景及解决方案

响应式丢失场景 Vuex 3 (Vue 2) Vuex 4 (Vue 3) Pinia (Vue 3) 通用解决方案
1. 直接修改 state ❌ 丢失响应式/不推荐 ⚠️ 可修改但破坏追踪 ✅ 允许直接修改 Vuex: 使用 mutations/actions Pinia: 可直接修改或使用 $patch
2. 动态添加 state 属性 ❌ 需要 Vue.set ⚠️ 部分支持 ✅ 自动响应式 Vuex: Vue.set(state, key, value) Pinia: 直接赋值
3. 动态删除 state 属性 ❌ 需要 Vue.delete ⚠️ 部分支持 ✅ 自动响应式 Vuex: Vue.delete(state, key) Pinia: delete state.key
4. 解构 state ❌ 丢失响应式 ❌ 丢失响应式 ⚠️ store 解构丢失,需用 storeToRefs 使用 mapStatetoRefsstoreToRefs
5. 直接替换 state 对象 ❌ 破坏响应式 ❌ 破坏响应式 ✅ 支持 $state 整体替换 Vuex: 逐个属性修改 Pinia: store.$state = newState
6. 模块中的嵌套状态 ⚠️ 需递归处理 ⚠️ 需递归处理 ✅ 自动响应式 Vuex: 预先定义结构 Pinia: 自动处理
7. getters 中返回新对象 ⚠️ 每次都重新计算 ⚠️ 每次都重新计算 ⚠️ 每次都重新计算 使用计算属性缓存,或返回 ref
8. 数组操作 ⚠️ 部分变异方法可用 ⚠️ 部分变异方法可用 ✅ 所有数组操作响应式 Vuex: 使用变异方法 Pinia: 直接操作
9. 异步操作后更新 ✅ 需 commit ✅ 需 commit ✅ 直接修改 Pinia 更简洁,无需 commit
10. 模块动态注册 ⚠️ 需特殊处理 ⚠️ 需特殊处理 ✅ 支持动态 store Vuex: registerModule Pinia: useStore 动态创建
11. 插件中修改 state ⚠️ 需谨慎 ⚠️ 需谨慎 ✅ 更安全 遵循各状态管理库的插件规范
12. SSR 中的状态 ⚠️ 需处理 ⚠️ 需处理 ✅ 内置支持 Pinia 的 SSR 支持更好

详细说明与代码示例

1. 直接修改 state

javascript 复制代码
// Vuex 3 (Vue 2)
// ❌ 直接修改(不推荐,破坏追踪)
this.$store.state.count = 10

// ✅ 正确方式
this.$store.commit('increment')
this.$store.dispatch('asyncIncrement')

// Vuex 4 (Vue 3)
// ⚠️ 可以直接修改但破坏 DevTools 追踪
store.state.count = 10

// ✅ 推荐方式
store.commit('increment')

// Pinia
// ✅ 允许直接修改
store.count = 10

// ✅ 更好的批量修改
store.$patch({ count: 10, name: 'Alice' })
store.$patch((state) => {
  state.count = 10
  state.name = 'Alice'
})

2-3. 动态添加/删除属性

javascript 复制代码
// Vuex 3
const store = new Vuex.Store({
  state: { user: {} }
})

// ❌ 直接添加
store.state.user.age = 25  // 非响应式

// ✅ 使用 Vue.set
Vue.set(store.state.user, 'age', 25)

// ❌ 直接删除
delete store.state.user.age  // 非响应式

// ✅ 使用 Vue.delete
Vue.delete(store.state.user, 'age')

// Vuex 4
// ⚠️ 添加属性可能响应式,但不保证
store.state.user.age = 25

// ✅ 推荐重新赋值
store.state.user = { ...store.state.user, age: 25 }

// Pinia
const useStore = defineStore('main', {
  state: () => ({ user: {} })
})

const store = useStore()

// ✅ 直接添加,完全响应式
store.user.age = 25
delete store.user.age  // 也支持删除

4. 解构 state

javascript 复制代码
// Vuex 3
// ❌ 直接解构丢失响应式
const { count, name } = this.$store.state

// ✅ 使用 mapState
computed: {
  ...mapState(['count', 'name'])
}

// ✅ 或使用辅助函数
computed: {
  count() {
    return this.$store.state.count
  }
}

// Vuex 4 (Composition API)
import { computed } from 'vue'
import { useStore } from 'vuex'

const store = useStore()

// ❌ 直接解构丢失响应式
const { count } = store.state

// ✅ 使用 computed
const count = computed(() => store.state.count)
const name = computed(() => store.state.name)

// Pinia
import { storeToRefs } from 'pinia'

const store = useStore()

// ❌ 直接解构丢失响应式
const { count, name } = store

// ✅ 使用 storeToRefs
const { count, name } = storeToRefs(store)

// ✅ 可以直接使用 store 的属性(模板中)
// 但在 JS 中需要 .value
console.log(count.value)

// ✅ actions 可以直接解构
const { increment } = store

5. 直接替换 state 对象

javascript 复制代码
// Vuex 3 & 4
// ❌ 直接替换整个 state 对象
store.state = { count: 0, name: 'Alice' }  // 破坏响应式

// ✅ 逐个属性替换
Object.assign(store.state, { count: 0, name: 'Alice' })

// ✅ 或使用 mutation
mutations: {
  replaceState(state, newState) {
    Object.assign(state, newState)
  }
}

// Pinia
const store = useStore()

// ✅ 支持整体替换
store.$state = { count: 0, name: 'Alice' }

// ✅ 也可以使用 $patch
store.$patch({ count: 0, name: 'Alice' })

6. 模块中的嵌套状态

javascript 复制代码
// Vuex 3
const store = new Vuex.Store({
  modules: {
    user: {
      state: {
        profile: {
          name: 'Alice'
        }
      }
    }
  }
})

// ❌ 动态添加深层属性
store.state.user.profile.age = 25  // 非响应式

// ✅ 使用 Vue.set
Vue.set(store.state.user.profile, 'age', 25)

// Pinia
const useUserStore = defineStore('user', {
  state: () => ({
    profile: {
      name: 'Alice'
    }
  })
})

const userStore = useUserStore()

// ✅ 自动响应式
userStore.profile.age = 25
userStore.profile.address = { city: 'Beijing' }  // 深层也响应式

7. getters 中返回新对象

javascript 复制代码
// Vuex 3 & 4
const store = new Vuex.Store({
  state: {
    items: [{ id: 1, name: 'A' }, { id: 2, name: 'B' }]
  },
  getters: {
    // ⚠️ 每次都返回新数组
    filteredItems: (state) => {
      return state.items.filter(item => item.id > 1)
    },
    
    // ✅ 返回缓存的计算结果
    cachedFilter: (state) => {
      return computed(() => state.items.filter(item => item.id > 1))
    }
  }
})

// Pinia
const useStore = defineStore('main', {
  state: () => ({
    items: [{ id: 1, name: 'A' }, { id: 2, name: 'B' }]
  }),
  getters: {
    // ⚠️ 仍然每次都重新计算
    filteredItems: (state) => {
      return state.items.filter(item => item.id > 1)
    },
    
    // ✅ 返回计算属性(缓存)
    filteredItemsComputed: (state) => {
      return computed(() => state.items.filter(item => item.id > 1))
    }
  }
})

8. 数组操作

javascript 复制代码
// Vuex 3 & 4
const store = new Vuex.Store({
  state: {
    list: [1, 2, 3]
  },
  mutations: {
    // ✅ 使用数组变异方法
    updateList(state) {
      state.list.push(4)        // 响应式
      state.list.splice(0, 1)   // 响应式
    },
    
    // ❌ 通过索引修改(Vuex 3 非响应式)
    badUpdate(state) {
      state.list[0] = 100  // Vuex 3 非响应式
    }
  }
})

// Pinia
const store = useStore()

// ✅ 所有数组操作都响应式
store.list.push(4)
store.list[0] = 100
store.list.length = 0
store.list.splice(0, 1)

9. 异步操作后更新

javascript 复制代码
// Vuex 3 & 4
const store = new Vuex.Store({
  state: { data: null },
  mutations: {
    setData(state, data) {
      state.data = data
    }
  },
  actions: {
    async fetchData({ commit }) {
      const data = await api.getData()
      commit('setData', data)  // 必须 commit
    }
  }
})

// Pinia
const useStore = defineStore('main', {
  state: () => ({ data: null }),
  actions: {
    async fetchData() {
      const data = await api.getData()
      this.data = data  // 直接修改,无需 commit
    }
  }
})

10. 模块动态注册

javascript 复制代码
// Vuex 3
// 动态注册模块
store.registerModule('newModule', {
  state: { count: 0 }
})

// ⚠️ 模块中的状态需要特殊处理
store.state.newModule.count = 10  // 可能非响应式

// Pinia
// ✅ 动态创建 store
const useDynamicStore = defineStore('dynamic', {
  state: () => ({ count: 0 })
})

const dynamicStore = useDynamicStore()  // 自动注册
dynamicStore.count = 10  // 完全响应式

11-12. 插件和 SSR

javascript 复制代码
// Pinia 插件示例
const piniaPlugin = (context) => {
  const { store } = context
  
  // ✅ 安全修改 state
  store.$patch({ extra: 'data' })
  
  // ✅ 订阅状态变化
  store.$subscribe((mutation, state) => {
    console.log('State changed', state)
  })
}

// SSR 中使用
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)

// 服务端预取数据
if (import.meta.env.SSR) {
  const store = useStore()
  await store.fetchData()
}

对比总结表

特性 Vuex 3 Vuex 4 Pinia
响应式原理 Object.defineProperty Proxy Proxy
直接修改 state ❌ 不推荐 ⚠️ 可修改但破坏追踪 ✅ 推荐
TypeScript 支持 ⚠️ 较弱 ⚠️ 一般 ✅ 优秀
模块化 ✅ 原生支持 ✅ 原生支持 ✅ 更简洁
动态添加属性 ❌ 需要 Vue.set ⚠️ 部分支持 ✅ 自动响应式
解构保持响应式 需要 mapState 需要 computed 需要 storeToRefs
代码量 较多 较多 较少
学习曲线 陡峭 陡峭 平缓
DevTools ✅ 成熟 ✅ 成熟 ✅ 现代化
体积 ~10KB ~10KB ~2KB

最佳实践建议

Vuex 3

javascript 复制代码
// ✅ 始终通过 mutations 修改
mutations: {
  updateUser(state, payload) {
    Vue.set(state.user, 'age', payload.age)
  }
}

// ✅ 使用 mapState 保持响应式
computed: {
  ...mapState(['user', 'count'])
}

Vuex 4

javascript 复制代码
// ✅ 使用 Composition API
import { computed } from 'vue'
const store = useStore()
const count = computed(() => store.state.count)

// ✅ 批量更新使用 Object.assign
mutations: {
  updateState(state, payload) {
    Object.assign(state, payload)
  }
}

Pinia

javascript 复制代码
// ✅ 推荐:直接修改 + $patch
const store = useStore()
store.count++
store.$patch({ name: 'Alice' })

// ✅ 解构时使用 storeToRefs
const { count, name } = storeToRefs(store)

// ✅ actions 中使用 this
actions: {
  async updateData() {
    const data = await fetch()
    this.data = data  // 直接修改
  }
}

迁移指南

Vuex 4 → Pinia 响应式相关迁移

javascript 复制代码
// Vuex 4
const store = new Vuex.Store({
  state: { count: 0, user: {} },
  mutations: {
    increment(state) {
      state.count++
    },
    setUser(state, user) {
      Vue.set(state, 'user', user)  // 需要特殊处理
    }
  },
  actions: {
    async fetchUser({ commit }) {
      const user = await api.getUser()
      commit('setUser', user)
    }
  }
})

// Pinia
const useStore = defineStore('main', {
  state: () => ({ count: 0, user: {} }),
  actions: {
    increment() {
      this.count++  // 直接修改
    },
    async fetchUser() {
      const user = await api.getUser()
      this.user = user  // 直接赋值,自动响应式
    }
  }
})

核心要点:

  • Vuex 3/4:响应式更新需要通过 mutations,动态属性需要特殊处理

  • Pinia:完全响应式,直接修改即可,更符合直觉

  • 迁移建议:从 Vuex 迁移到 Pinia 时,可以大幅简化状态更新逻辑


Vue 2/3 及状态管理库开发注意事项完整总结

注意事项分类 Vue 2 Vue 3 Vuex 3 Vuex 4 Pinia
生命周期钩子 beforeCreate, created beforeMount, mounted beforeUpdate, updated beforeDestroy, destroyed setup 替代 beforeCreate/created onBeforeMount, onMounted onBeforeUpdate, onUpdated onBeforeUnmount, onUnmounted beforeCreate/created 中可访问 setup 中通过 useStore() setup 中使用,支持组合式 API
组件通信 $emit, $on, $off, $parent $children, $refs, provide/inject emit, props, $refs provide/inject, mitt 替代事件总线 dispatch, commit mapActions, mapMutations 同 Vuex 3 store 直接调用 store.$patch
响应式数据定义 data() 返回对象 Vue.set 添加属性 ref, reactive, toRefs computed, readonly state 函数返回对象 同 Vuex 3 state 函数返回对象 支持 ref/reactive
模板语法限制 每个组件只能一个根元素 支持多个根元素(Fragment) N/A N/A N/A
异步组件 () => import('./Comp.vue') Vue.component('async', resolve) defineAsyncComponent 支持 Suspense import() 动态导入模块 同 Vuex 3 支持动态 store 导入
TypeScript 支持 ⚠️ 类型推断弱 需要 vue-class-component ✅ 优秀的内置支持 完善的类型推断 ⚠️ 需要额外类型定义 ⚠️ 部分支持 ✅ 优秀的内置支持
性能优化 keep-alive, v-once v-show, 异步组件 Object.freeze v-memo, v-once shallowRef, shallowReactive Tree-shaking 优化 模块懒加载 命名空间模块 同 Vuex 3 自动 tree-shaking 按需加载
内存泄漏风险 全局事件总线 未销毁的定时器 未解绑的 DOM 事件 同 Vue 2 但组合式 API 更易清理 未注销的模块 大量状态积累 同 Vuex 3 自动清理未使用的 store
SSR 注意事项 避免浏览器 API 使用 created 而非 mounted 使用 this.$isServer 使用 onMounted 判断 <ClientOnly> 组件 useSSRContext 避免在 actions 中使用浏览器 API 同 Vuex 3 内置 SSR 支持 $subscribe 在 SSR 中谨慎使用
打包体积优化 完整版 32KB 运行时版 22KB 支持 tree-shaking 按需引入 API 完整版 ~10KB 同 Vuex 3 ~2KB 按需引入
调试工具 Vue Devtools 成熟 Vue Devtools 功能更强 Vue Devtools 集成 Vue Devtools 集成 Pinia Devtools 专用
测试便利性 需要 mock 很多 API @vue/test-utils 组合式 API 易测试 @vue/test-utils 改进 需要 mock store 同 Vuex 3 易于 mock 和测试
国际化 (i18n) vue-i18n@8 $t 全局方法 vue-i18n@9 Composition API 支持 需集成到 store 同 Vuex 3 集成简单
路由集成 vue-router@3 $route, $router vue-router@4 useRoute, useRouter 路由守卫中使用 store 同 Vuex 3 路由守卫中直接使用
错误处理 errorCaptured Vue.config.errorHandler errorCaptured app.config.errorHandler 插件中的错误处理 同 Vuex 3 插件中的错误处理 $onAction 错误捕获

详细说明与代码示例

1. 生命周期钩子

html 复制代码
<!-- Vue 2 -->
<script>
export default {
  data() {
    return { count: 0 }
  },
  beforeCreate() {
    console.log('实例初始化前')
  },
  created() {
    console.log('实例创建完成')
  },
  beforeMount() {
    console.log('挂载前')
  },
  mounted() {
    console.log('挂载完成')
  },
  beforeDestroy() {
    console.log('销毁前')
  },
  destroyed() {
    console.log('销毁完成')
  }
}
</script>

<!-- Vue 3 -->
<script setup>
import { ref, onBeforeMount, onMounted, onBeforeUnmount, onUnmounted } from 'vue'

const count = ref(0)

onBeforeMount(() => {
  console.log('挂载前')
})

onMounted(() => {
  console.log('挂载完成')
})

onBeforeUnmount(() => {
  console.log('卸载前')
})

onUnmounted(() => {
  console.log('卸载完成')
})
</script>

2. 组件通信

javascript 复制代码
// Vue 2 通信方式
// 1. props/$emit
this.$emit('update', data)

// 2. 事件总线
Vue.prototype.$bus = new Vue()
this.$bus.$on('event', handler)
this.$bus.$emit('event', data)

// 3. $parent/$children
this.$parent.method()
this.$children[0].method()

// 4. provide/inject
provide() { return { data: this.data } }
inject: ['data']

// Vue 3 通信方式
// 1. props/emit
const emit = defineEmits(['update'])
emit('update', data)

// 2. mitt (替代事件总线)
import mitt from 'mitt'
const emitter = mitt()
emitter.on('event', handler)
emitter.emit('event', data)

// 3. provide/inject
provide('key', ref(data))
const data = inject('key')

// 4. $refs (Composition API)
const childRef = ref()
childRef.value.method()

3. 响应式数据定义

javascript 复制代码
// Vue 2
export default {
  data() {
    return {
      count: 0,
      user: { name: 'Alice' }
    }
  },
  methods: {
    addProp() {
      // ❌ 错误:不会响应式
      this.user.age = 25
      // ✅ 正确
      this.$set(this.user, 'age', 25)
    }
  }
}

// Vue 3
import { ref, reactive, toRefs } from 'vue'

const count = ref(0)           // 基础类型
const state = reactive({       // 对象类型
  count: 0,
  user: { name: 'Alice' }
})

// ✅ 自动响应式
state.user.age = 25

// 解构保持响应式
const { user } = toRefs(state)

4. 模板语法限制

html 复制代码
<!-- Vue 2: 必须有根元素 -->
<template>
  <div>  <!-- 必须有一个根元素 -->
    <h1>Title</h1>
    <p>Content</p>
  </div>
</template>

<!-- Vue 3: 支持多根元素 -->
<template>
  <h1>Title</h1>
  <p>Content</p>  <!-- 不需要包裹 -->
</template>

5. 异步组件

javascript 复制代码
// Vue 2
Vue.component('async-comp', () => import('./Comp.vue'))

// 或
const AsyncComp = () => import('./Comp.vue')

// Vue 3
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => 
  import('./Comp.vue')
)

// 高级用法
const AsyncComp = defineAsyncComponent({
  loader: () => import('./Comp.vue'),
  loadingComponent: LoadingComp,
  errorComponent: ErrorComp,
  delay: 200,
  timeout: 3000
})

6. TypeScript 支持

TypeScript 复制代码
// Vue 2 (需要装饰器)
import Vue from 'vue'
import Component from 'vue-class-component'

@Component
export default class MyComp extends Vue {
  count: number = 0
  increment(): void {
    this.count++
  }
}

// Vue 3 (原生支持)
<script setup lang="ts">
import { ref } from 'vue'

interface User {
  name: string
  age: number
}

const count = ref<number>(0)
const user = ref<User>({ name: 'Alice', age: 25 })
</script>

// Pinia TypeScript
interface MainState {
  count: number
  user: User | null
}

export const useMainStore = defineStore('main', {
  state: (): MainState => ({
    count: 0,
    user: null
  }),
  actions: {
    async fetchUser(id: number): Promise<void> {
      // 完全类型推断
      this.user = await api.getUser(id)
    }
  }
})

7. 性能优化

javascript 复制代码
// Vue 2 优化
<template>
  <div>
    <!-- 静态内容只渲染一次 -->
    <div v-once>{{ staticContent }}</div>
    
    <!-- 列表优化 -->
    <div v-for="item in items" :key="item.id">
      {{ item.name }}
    </div>
  </div>
</template>

<script>
export default {
  computed: {
    // 冻结不需要响应式的数据
    frozenData() {
      return Object.freeze(largeData)
    }
  }
}
</script>

// Vue 3 优化
<script setup>
import { shallowRef, shallowReactive, vMemo } from 'vue'

// 浅响应式,优化大对象
const shallowState = shallowReactive({ 
  largeData: { /* 大量数据 */ }
})

// 浅 ref
const shallowArray = shallowRef([1, 2, 3])

// 使用 v-memo 缓存模板片段
</script>

<template>
  <div v-memo="[item.id]">
    <!-- 只有 item.id 变化时才重新渲染 -->
    <p>{{ item.name }}</p>
  </div>
</template>

8. 内存泄漏预防

javascript 复制代码
// Vue 2 预防
export default {
  data() {
    return {
      timer: null
    }
  },
  mounted() {
    // 定时器
    this.timer = setInterval(() => {}, 1000)
    
    // 全局事件
    window.addEventListener('resize', this.handleResize)
    
    // 事件总线
    this.$bus.$on('event', this.handler)
  },
  beforeDestroy() {
    // 清理
    clearInterval(this.timer)
    window.removeEventListener('resize', this.handleResize)
    this.$bus.$off('event', this.handler)
  }
}

// Vue 3 预防
<script setup>
import { onMounted, onUnmounted } from 'vue'

let timer = null
const handleResize = () => {}

onMounted(() => {
  timer = setInterval(() => {}, 1000)
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  clearInterval(timer)
  window.removeEventListener('resize', handleResize)
})
</script>

9. SSR 注意事项

javascript 复制代码
// Vue 2 SSR
export default {
  mounted() {
    // ✅ 只在客户端执行
    if (!this.$isServer) {
      this.clientOnlyMethod()
    }
  },
  created() {
    // ❌ 避免在 created 中使用浏览器 API
    // window.location 会报错
  }
}

// Vue 3 SSR
<script setup>
import { onMounted } from 'vue'

// ✅ 使用 onMounted,只在客户端执行
onMounted(() => {
  document.title = 'Title'
})

// ✅ 使用 ClientOnly 组件
</script>

<template>
  <ClientOnly>
    <ClientComponent />
  </ClientOnly>
</template>

10. 状态管理注意事项

javascript 复制代码
// Vuex 3/4 注意事项
const store = new Vuex.Store({
  state: {
    count: 0,
    user: null
  },
  mutations: {
    // ✅ 同步操作
    increment(state) {
      state.count++
    },
    // ❌ 不要在 mutation 中做异步
    asyncBadMutation(state) {
      const data = await fetch()  // 错误
    }
  },
  actions: {
    // ✅ 异步操作放在 actions
    async fetchUser({ commit }) {
      const user = await api.getUser()
      commit('setUser', user)
    }
  }
})

// Pinia 注意事项
const useStore = defineStore('main', {
  state: () => ({
    count: 0,
    user: null
  }),
  actions: {
    // ✅ 异步操作直接在 actions
    async fetchUser() {
      const user = await api.getUser()
      this.user = user  // 直接修改
    },
    
    // ✅ 支持直接异步
    async incrementAsync() {
      await delay(1000)
      this.count++
    }
  },
  
  // ⚠️ getters 不能是异步的
  getters: {
    // ❌ 错误:getter 不能异步
    asyncData: (state) => {
      return await fetch()  // 错误
    }
  }
})

开发检查清单

Vue 2 特有检查项

  • 是否使用了 $set 添加新属性

  • 模板是否有唯一根元素

  • 是否清理了全局事件总线

  • 异步组件是否正确注册

Vue 3 特有检查项

  • 是否正确使用 ref/reactive

  • 解构时是否使用 toRefs/storeToRefs

  • 是否利用 shallowRef 优化大对象

  • 是否正确使用组合式 API 生命周期

状态管理通用检查项

  • Vuex 3/4: mutations 是否都是同步的

  • Vuex 3/4: 动态属性是否使用 Vue.set

  • Pinia: 解构是否使用 storeToRefs

  • Pinia: 是否正确使用 $patch 批量更新

  • 模块是否正确命名空间(Vuex)

  • 是否避免了在 getters 中产生副作用

性能检查项

  • 大列表是否使用 v-oncev-memo

  • 是否按需引入组件和库

  • 是否正确使用 keep-alive

  • 路由是否懒加载

  • 是否避免了不必要的响应式数据


迁移注意事项对照表

迁移场景 Vue 2 → Vue 3 Vuex 3 → Vuex 4 Vuex → Pinia
生命周期 钩子函数改为组合式 API 基本不变 使用组合式 API
响应式 dataref/reactive 基本不变 state 支持 ref/reactive
组件通信 移除 $on/$off/$once 基本不变 直接调用 store 方法
类型支持 需要装饰器 → 原生 TS 类型增强 完美的 TS 支持
模块化 基本不变 基本不变 更简洁的模块系统
代码量 减少 30-50% 基本不变 减少 40-60%

核心开发原则:

  1. Vue 3: 优先使用组合式 API,充分利用 Proxy 响应式

  2. Pinia: 简单直接的状态管理,避免过度设计

  3. 性能: 合理使用浅响应式 API 优化大对象

  4. 内存: 及时清理副作用和事件监听

  5. 类型: 充分利用 TypeScript 的类型推断

相关推荐
Java陈序员21 小时前
自建 Claude Code 镜像!一站式开源中转服务!
docker·node.js·vue·claude·claude code
呆头鸭L1 天前
Electron进程通信
前端·javascript·electron·前端框架·vue
木斯佳1 天前
前端八股文面经大全: 蓝色光标前端一面OC(2026-03-23)·面经深度解析
前端·面试·vue·校招·js·面经
蜡台2 天前
SPA(Single Page Application) Web 应用(即单页应用)架构模式 更新
前端·架构·vue·react·spa·spa更新
SuperEugene2 天前
Vue3 Pinia 状态管理规范:状态拆分、Actions 写法、持久化实战,避坑状态污染|状态管理与路由规范篇
前端·javascript·vue.js·前端框架·pinia
Betelgeuse762 天前
Django 项目远程服务器部署教程:从开发到生产
python·django·vue
初级见习猿工2 天前
使用pdfjs-dist在Vue 3中实现PDF文件浏览器预览
javascript·vue·pdfjs-dist
A_nanda2 天前
ZR.Admin.NET后台管理系统
vue·.net·zradmin
十七号程序猿3 天前
Java图书管理系统 | 无需配置任何环境,双击一键启动,开箱即用
java·spring boot·vue·毕业设计·毕设·源代码管理