watchEffect 无法直接监听 window 对象,因 window 非响应式;需用 ref 包装 innerWidth/innerHeight 并在 resize 中手动更新,再由 watchEffect 追踪 ref 变化实现响应。

用 watchEffect 实时响应窗口尺寸变化,核心在于:把 window.innerWidth 和 window.innerHeight 包装成响应式访问,并让副作用函数自动追踪它们。
为什么不能直接监听 window 对象?
window 本身不是响应式对象,Vue 无法自动 track 它的属性变化。所以不能写 watchEffect(() => console.log(window.innerWidth)) 就完事——虽然它会执行一次,但后续 resize 时不会重新触发。
必须通过一个“响应式中介”暴露尺寸值,常见做法是用 ref 存储并手动更新:
- 定义
width = ref(window.innerWidth)、height = ref(window.innerHeight) - 在
resize事件中同步更新这两个 ref - 再让
watchEffect读取它们 → 触发依赖收集 → 后续变更自动重跑
标准实现:ref + resize 事件监听
这是最清晰、可控、无副作用泄漏的方式:
立即学习“前端免费学习笔记(深入)”;
递归分析 Vue 项目组件依赖,从入口文件生成组件层级图,支持 Vue 2/3,输出组件名、文件路径和属性。适用于分析组件结构、排查依赖或了解项目架构。
import { ref, watchEffect, onUnmounted } from 'vue'
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
const resizeHandler = () => {
width.value = window.innerWidth
height.value = window.innerHeight
}
window.addEventListener('resize', resizeHandler)
// watchEffect 自动追踪 width/height 的读取
watchEffect(() => {
console.log(`尺寸: ${width.value} × ${height.value}`)
// 可在此做适配逻辑,如切换布局、调整 canvas 大小等
})
// 组件卸载时清理事件监听器
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
})
进阶技巧:封装成可复用的组合式函数
把上述逻辑抽成 useWindowSize(),便于多处复用:
import { ref, onMounted, onUnmounted } from 'vue'
export function useWindowSize() {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
const update = () => {
width.value = window.innerWidth
height.value = window.innerHeight
}
onMounted(() => {
window.addEventListener('resize', update)
})
onUnmounted(() => {
window.removeEventListener('resize', update)
})
return { width, height }
}
在组件中使用:
import { watchEffect } from 'vue'
import { useWindowSize } from '@/composables/useWindowSize'
const { width, height } = useWindowSize()
watchEffect(() => {
if (width.value < 768) {
console.log('移动端布局')
} else {
console.log('桌面端布局')
}
})
注意防抖与竞态(尤其涉及 DOM 操作时)
频繁的 resize 事件可能每秒触发数十次,若副作用里含 DOM 更新或计算密集操作,需主动节流:
- 不推荐在
watchEffect内部直接写setTimeout节流,容易破坏响应式链路 - 更稳妥的做法是在
resizeHandler中加防抖(例如用lodash.debounce或简易 timer) - 如果副作用涉及异步请求(如根据屏幕宽度加载不同资源),记得用
onInvalidate清理上一轮未完成任务
示例(带防抖):
let resizeTimer
const resizeHandler = () => {
clearTimeout(resizeTimer)
resizeTimer = setTimeout(() => {
width.value = window.innerWidth
height.value = window.innerHeight
}, 16) // 约 60fps
}
本质上,watchEffect 不是“监听 window”,而是监听你主动让它看到的响应式数据。只要把尺寸变化转化为 ref 的变更,它就能精准响应,且自动清理、无需手动 stop。

















