Code前端首页关于Code前端联系我们

Vue 3里计算属性不更新是咋回事?该咋解决?

terry 1小时前 阅读数 10 #SEO
文章标签 Vue3计算属性

做Vue开发时,不少同学会碰到这样的情况:明明数据改了,计算属性却纹丝不动,页面也没跟着变,尤其是刚从Vue 2转到Vue 3,对响应式原理、Composition API写法不太熟的同学,更容易栽跟头,今天就把“计算属性不更新”的常见坑挨个拆了,讲讲咋解决。

响应式数据基础没打好,计算属性“瞎”了

Vue 3靠refreactive让数据变成“响应式”的——数据变了,依赖它的计算属性、视图才会跟着变,要是计算属性依赖的不是响应式数据,那数据再怎么改,计算属性也感知不到。

举个例子:想做个“计数翻倍”的功能,有人这么写:

// 错误示范:count不是响应式数据
let count = 0  
const doubleCount = computed(() => count * 2)  
function increment() {  
  count++ // 这里改的是普通变量,不是响应式数据  
}  

点按钮执行increment时,count确实变了,但doubleCount完全没反应——因为count是普通变量,Vue没法跟踪它的变化。

解决办法:用ref把数据包成响应式,修改后:

// 正确示范:count是ref响应式数据
const count = ref(0)  
const doubleCount = computed(() => count.value * 2)  
function increment() {  
  count.value++ // 改响应式数据的value  
}  

这样count.value变化时,doubleCount会自动重新计算。

再延伸一下:如果用reactive包对象,直接替换整个对象也会出问题。

const state = reactive({ name: 'Alice' })  
const userName = computed(() => state.name)  
// 错误操作:直接替换state,旧的响应式对象丢了  
state = { name: 'Bob' }  

这时候userName依赖的是旧state,所以不会更新,要改对象属性,而不是替换整个对象:

state.name = 'Bob' // 改属性,响应式能跟踪到  

计算属性写法踩了坑,逻辑走偏了

计算属性的核心是返回一个值,如果写法不对,要么返回值错了,要么逻辑没走对,结果自然不对。

坑1:忘记返回值

有人写计算属性时,把逻辑塞进去却忘了return

const list = ref([1, 2, 3, 4])  
// 错误:过滤后没return,filteredList拿到undefined  
const filteredList = computed(() => {  
  list.value.filter(num => num > 2)  
})  

这时候filteredList一直是undefined,后续list变化也没人管。解决:加上return

const filteredList = computed(() => {  
  return list.value.filter(num => num > 2)  
})  

坑2:把计算属性当方法用

计算属性是缓存的(依赖不变时复用结果),如果想每次调用都执行逻辑,该用方法(比如function filterList() { ... }),但有人硬把方法逻辑塞到计算属性里,结果依赖没变化时,以为计算属性“没更新”,其实是缓存机制在起作用。

比如想做“随机选一个列表项”,有人写成:

const list = ref(['a', 'b', 'c'])  
// 错误:计算属性依赖list,但Math.random()不是响应式依赖  
const randomItem = computed(() => {  
  return list.value[Math.floor(Math.random() * list.value.length)]  
})  

每次渲染时,randomItem看似没变化(其实是因为list没改,计算属性复用缓存了),这时候该用方法,而不是计算属性:

function getRandomItem() {  
  return list.value[Math.floor(Math.random() * list.value.length)]  
}  

依赖项没被正确跟踪,计算属性“聋”了

计算属性能自动跟踪依赖,但如果依赖是对象深层属性或者被错误解构,Vue就没法跟踪变化,导致计算属性不更新。

坑:对象解构丢了响应式

reactive定义对象后,直接解构会丢失响应式。

const state = reactive({ user: { name: 'Alice' } })  
// 错误:解构后user不是响应式ref  
const { user } = state  
const userName = computed(() => user.name)  
// 当state.user被替换时,user还是旧对象  
state.user = { name: 'Bob' }  

这时候userName依赖的是旧username,所以不会更新。解决:用toRefs把对象属性转成ref:

import { toRefs } from 'vue'  
const state = reactive({ user: { name: 'Alice' } })  
const { user } = toRefs(state) // user变成ref,user.value是响应式对象  
const userName = computed(() => user.value.name)  
state.user = { name: 'Bob' } // user.value会更新,userName也跟着更新  

延伸:数组操作的响应式

Vue 3对数组的响应式支持比Vue 2好,直接改索引(比如list.value[0] = 'new')能触发更新,但如果是替换整个数组(比如list.value = [1,2,3]),只要listrefreactive的,计算属性也能跟踪到,真正要注意的还是对象深层属性的解构问题~

把computed和watch的活儿搞混了

computed自动依赖+缓存,适合“根据已有数据生成新数据”;watch主动监听+执行副作用,适合“数据变化后做异步、DOM操作”,用错场景,就会以为计算属性没更新。

坑:在computed里写异步逻辑

有人想在计算属性里发请求,

// 错误:computed期望同步返回值,异步返回Promise  
const userInfo = computed(async () => {  
  const res = await fetch('/api/user')  
  return res.data  
})  

这时候userInfo拿到的是Promise,既没法正常渲染,后续数据变化也不会触发重新请求。解决:用watch或生命周期钩子发请求,把结果存在响应式数据里,再让计算属性依赖它:

const userInfo = ref(null)  
watch(  
  () => route.params.id, // 监听路由参数变化  
  async (id) => {  
    const res = await fetch(`/api/user/${id}`)  
    userInfo.value = res.data  
  },  
  { immediate: true }  
)  
// 计算属性依赖userInfo  
const userNickname = computed(() => userInfo.value?.nickname)  

异步操作里的数据变化,计算属性没跟上

setTimeoutPromise回调里改数据时,要是操作的不是响应式数据,计算属性也不会更新。

举个例子:想延迟1秒改计数,但操作了普通变量:

let rawCount = 0  
const count = ref(rawCount)  
const doubleCount = computed(() => count.value * 2)  
setTimeout(() => {  
  rawCount++ // 改的是普通变量,count.value还是0  
}, 1000)  

这时候doubleCount还是0,因为count.value没变化。解决:直接改响应式数据的value

setTimeout(() => {  
  count.value++ // 改响应式数据,计算属性会更新  
}, 1000)  

如果用了第三方非响应式状态(比如Redux的store),Vue计算属性依赖它时,因为没法跟踪变化,也会不更新,这时候得把第三方状态转成Vue的响应式数据(比如用ref包一下),或者用watch手动同步。

避开这些坑,计算属性乖乖听话

想让Vue 3的计算属性正常更新,核心是抓住这几点:

  1. 响应式基础:依赖的数据必须用ref/reactive包成响应式,别用普通变量。
  2. 写法正确:计算属性要返回值,别把异步、副作用逻辑塞进去。
  3. 依赖跟踪:对象深层属性用toRefs解构,别让响应式“断链”。
  4. 场景分清computed负责“数据推导”,watch负责“副作用”,别搞混。
  5. 异步操作:异步里改数据,一定要操作响应式数据的value或属性。

把这些逻辑理顺,计算属性就能精准响应数据变化,页面也能跟着乖乖更新啦~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门