Vue3里computed和onMounted咋用?常见场景+原理一次讲清
computed在Vue3中是做什么的?
简单说,computed 是Vue3提供的响应式计算属性API,核心作用是基于其他响应式数据,生成一个自动更新的“动态值”。
举个生活例子:点外卖时,购物车的“总价”需根据每个商品的“单价×数量”自动计算,且商品数量变化时总价得实时更新——这就是computed的典型场景。
从技术角度看,computed有两个关键特性:
- 依赖追踪:自动“盯住”用到的响应式数据(如
ref/reactive包裹的数据),一旦这些数据变化,computed结果会自动更新。 - 缓存机制:若依赖数据没变化,多次访问
computed结果时,会直接用之前计算的缓存值,不会重复执行计算逻辑——这比每次调用methods里的函数性能更好(methods每次调用都要重新计算)。
看段代码直观理解:
<template>
<div>总价:{{ totalPrice }}</div>
<button @click="cart[0].count++">增加商品1数量</button>
</template>
<script setup>
import { reactive, computed } from 'vue'
// 购物车数据(响应式)
const cart = reactive([
{ price: 10, count: 2 },
{ price: 20, count: 3 }
])
// 计算总价:依赖cart里的price和count
const totalPrice = computed(() => {
return cart.reduce((sum, item) => sum + item.price * item.count, 0)
})
</script>
点击按钮修改cart[0].count时,totalPrice会自动重新计算——因为computed追踪到cart变化;若连续多次访问totalPrice,只要cart没动,就直接读缓存,无需重复执行reduce。
computed的响应式原理是怎么实现的?
Vue3的响应式基于Proxy代理,但computed的实现更“聪明”——结合了effect(副作用)和懒执行+缓存逻辑。
简单拆解原理:
- 懒执行:创建
computed时,不会立刻执行计算逻辑,只有访问计算属性(如模板里用{{ totalPrice }})时,才会执行回调函数算出结果。 - 依赖收集:执行回调时,Vue通过
track方法,把computed自身注册为依赖数据(如上述cart)的“观察者”。 - 标记脏值(dirty):当依赖数据(
cart)变化时,Vue通过trigger触发更新,把computed标记为“脏”(结果过时)。 - 缓存复用:下次访问
computed时,先检查是否“脏”——若脏了,重新计算并更新缓存;若没脏,直接返回缓存值。
打个比方:computed像“自动更新的计算器”,只有输入(依赖数据)变化才重新算结果;否则用上次结果,省得重复干活,这种设计能大幅减少不必要计算,让页面更流畅。
onMounted钩子在什么时机执行?
onMounted是Vue3组件生命周期钩子,执行时机是:组件对应的DOM完全渲染到页面后。
组件挂载分两步:先创建虚拟DOM,再把虚拟DOM渲染成真实DOM。onMounted是挂载阶段“最后一步”,此时能安全操作真实DOM(如获取元素尺寸、初始化依赖DOM的库)。
举个常见场景:用ECharts做图表,ECharts需先拿到页面上的DOM容器才能初始化图表,若在onMounted前执行,DOM未渲染,会拿到null,导致初始化失败,看代码:
<template>
<div ref="chartRef" style="width: 600px; height: 400px;"></div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import * as echarts from 'echarts'
const chartRef = ref(null) // 初始是null,DOM渲染后才赋值
onMounted(() => {
const dom = chartRef.value // 此时DOM已渲染,dom能拿到真实元素
const myChart = echarts.init(dom)
myChart.setOption({ /* 图表配置 */ })
})
</script>
对比onBeforeMount(DOM渲染前执行):此时chartRef.value还是null,拿不到DOM,无法初始化图表。
computed和onMounted能配合使用吗?有哪些场景?
当然能!onMounted负责“初始化数据/环境”,computed负责“基于数据做实时计算”,很多真实项目都会这么用。
举个博客系统例子:页面加载后(onMounted)请求文章列表,computed根据文章“点赞数”实时计算“热门文章”,代码如下:
<template>
<h2>热门文章</h2>
<ul>
<li v-for="article in hotArticles" :key="article.id">{{ article.title }}</li>
</ul>
</template>
<script setup>
import { onMounted, reactive, computed } from 'vue'
// 存储所有文章(响应式)
const articles = reactive([])
// 模拟接口请求(实际用axios/fetch)
function fetchArticles() {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ id: 1, title: 'Vue3入门', likes: 100 },
{ id: 2, title: 'React技巧', likes: 80 },
{ id: 3, title: 'JS进阶', likes: 120 }
])
}, 1000)
})
}
// 页面挂载后,请求文章数据
onMounted(async () => {
const res = await fetchArticles()
articles.push(...res)
})
// 计算属性:按点赞数降序排列,得到热门文章
const hotArticles = computed(() => {
return [...articles].sort((a, b) => b.likes - a.likes)
})
</script>
逻辑链清晰:
onMounted触发→请求文章数据→articles被填充(响应式数据变化)。hotArticles依赖articles→articles变化时,hotArticles自动重新排序。
若后续用户给文章点赞(如加按钮修改article.likes),hotArticles也会实时更新排序——因为computed始终盯着articles的变化。
用computed容易踩哪些坑?怎么避免?
computed好用但新手易踩“陷阱”,常见问题及解决方法如下:
陷阱1:依赖非响应式数据
若computed里用了普通变量(没被ref/reactive包裹),数据变化时computed不会更新。
反面例子:
<script setup>
import { computed } from 'vue'
let count = 0 // 普通变量,非响应式
const double = computed(() => count * 2)
function add() {
count++ // double不会更新!因count非响应式
}
</script>
解决:把普通变量改成响应式(用ref):
const count = ref(0) // count变为响应式,变化时double会更新
陷阱2:在computed里写异步操作
computed的回调必须同步返回值,不能用async/await搞异步,若要处理异步逻辑,得换watch或在setup里结合ref。
反面例子(错误用法):
const asyncComputed = computed(async () => {
const res = await fetch('/api/data')
return res.data // 不行!computed不支持异步回调
})
解决思路:用watch监听触发异步的条件,把结果存在ref里,再让computed依赖这个ref:
const data = ref(null)
watch(someTrigger, async () => {
const res = await fetch('/api/data')
data.value = res.data
})
const computedData = computed(() => {
return data.value ? transform(data.value) : null
})
陷阱3:过度依赖computed导致逻辑臃肿
若多个computed嵌套或计算逻辑太复杂,代码会难以维护。
解决:把复杂逻辑拆成多个小computed,或用methods做辅助计算(虽methods无缓存,但可读性优先时可用)。
把“过滤”和“排序”拆成两个computed:
// 先过滤
const filteredList = computed(() => {
return list.value.filter(item => item.status === 'active')
})
// 再排序
const sortedList = computed(() => {
return [...filteredList.value].sort((a, b) => a.time - b.time)
})
onMounted里操作DOM要注意什么?
onMounted能拿到真实DOM,但这些细节要注意,否则易出bug:
注意1:动态渲染的DOM(v-if控制)
若DOM是用v-if控制显示的,onMounted执行时,要确保该DOM已被渲染。
反面例子:
<template>
<div v-if="show" ref="boxRef">我是动态DOM</div>
<button @click="show = !show">切换</button>
</template>
<script setup>
import { onMounted, ref } from 'vue'
const show = ref(false)
const boxRef = ref(null)
onMounted(() => {
console.log(boxRef.value) // 初始show是false,DOM未渲染→输出null
})
</script>
解决:用watch监听show变化,在show为true时操作DOM:
watch(show, (newVal) => {
if (newVal) {
// show为true时,DOM已渲染,可安全操作boxRef.value
console.log(boxRef.value)
}
})
注意2:避免在onMounted里做耗时操作
若onMounted里有大量循环、DOM操作或复杂计算,会阻塞页面渲染,让用户觉得“页面加载慢”。
解决:把耗时逻辑拆成异步任务(如用setTimeout包装,或用Web Worker),让页面先渲染,再后台执行逻辑。
拓展:computed的get和set怎么用?
默认computed只有“获取值”的逻辑(getter),但也能定义“设置值”的逻辑(setter),实现双向响应。
比如做“搜索过滤”功能:输入框绑定searchText,computed的filteredList既能根据searchText过滤列表,也能通过设置filteredList修改searchText,代码如下:
<template>
<input v-model="searchText" placeholder="搜索">
<ul>
<li v-for="item in filteredList" :key="item.id">{{ item.name }}</li>
</ul>
<button @click="resetSearch">重置搜索</button>
</template>
<script setup>
import { ref, computed } from 'vue'
const list = ref([
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' }
])
const searchText = ref('')
// 带setter的computed
const filteredList = computed({
get() {
return list.value.filter(item => item.name.includes(searchText.value))
},
set(newValue) {
// 当给filteredList赋值时,触发setter,更新searchText
searchText.value = newValue
}
})
function resetSearch() {
filteredList.value = '' // 触发setter,searchText被设为'',列表恢复全部内容
}
</script>
点击“重置搜索”按钮时,filteredList.value = ''会触发setter,把searchText设为,进而让getter重新计算,列表显示所有内容——这就是computed setter的妙用:让计算属性既能“读”又能“写”。
onMounted和其他生命周期钩子有啥区别?
Vue3的生命周期钩子很多,onMounted的独特性在于只在组件第一次渲染完成后执行,后续组件更新(如数据变化导致重新渲染)时,不会再触发onMounted,而是触发onUpdated。
举个对比表格(简化版):
| 钩子名 | 执行时机 | 典型场景 |
|---|---|---|
| onBeforeMount | 组件挂载前(DOM未渲染) | 提前准备数据(少用,一般用setup) |
| onMounted | 组件挂载后(DOM已渲染) | 初始化DOM相关逻辑(图表、地图) |
| onBeforeUpdate | 数据变化后,DOM更新前 | 记录DOM更新前的状态 |
| onUpdated | 数据变化后,DOM更新完成后 | 调整DOM样式(比如滚动位置) |
举个定时器例子:
<script setup>
import { onMounted, onUnmounted } from 'vue'
let timer = null
onMounted(() => {
timer = setInterval(() => {
console.log('每1秒执行')
}, 1000)
})
onUnmounted(() => {
clearInterval(timer) // 组件销毁时清除定时器,避免内存泄漏
})
</script>
onMounted里启动定时器,onUnmounted(组件销毁时)清除定时器——这是常见的“生命周期配对”写法。
computed是“响应式的智能计算器”,基于依赖数据生成动态结果;onMounted是“DOM就绪后的初始化入口”,处理依赖真实DOM的逻辑,两者单独使用解决不同问题,配合起来能打造更灵活的交互(如数据请求+实时计算)。
实际开发中,要注意computed的依赖必须是响应式数据、避免异步操作;onMounted要关注DOM渲染时机、别塞太多耗时逻辑,吃透这些细节,Vue3的响应式和生命周期就能用得更顺手~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



