本文解决 Vue 3 组合式 API 下使用自定义 Hook 管理主题状态时, 无法响应式切换白天/夜间 Banner 图片的问题——根本原因在于 useTheme 每次调用都创建了独立的 ref 实例,导致状态未真正共享。
本文解决 vue 3 组合式 api 下使用自定义 hook 管理主题状态时,`` 无法响应式切换白天/夜间 banner 图片的问题——根本原因在于 `usetheme` 每次调用都创建了独立的 `ref` 实例,导致状态未真正共享。
在 Vue 3 的组合式 API 开发中,自定义 Hook(如 useTheme)常被用于逻辑复用。但若设计不当,极易引发“看似调用同一 Hook,实则状态隔离”的陷阱。你遇到的问题正是典型场景:App.vue 和 Home.vue 都调用了 useTheme(),但图片未实时更新——这是因为原代码中 theme = ref(...) 被定义在函数内部,每次调用 useTheme() 都会新建一个 ref,各组件持有的是彼此无关的响应式变量,自然无法触发统一更新。
✅ 正确做法是将 ref 声明提升至模块顶层(即函数外部),确保所有组件共享同一个响应式引用:
// composables/useTheme.js
import { ref, watchEffect } from 'vue'
// ✅ 全局唯一响应式引用 —— 在模块作用域内初始化
const theme = ref(JSON.parse(localStorage.getItem('theme') || 'false'))
// ✅ 自动同步到 localStorage(watchEffect 依赖 theme.value)
watchEffect(() => {
localStorage.setItem('theme', String(theme.value))
})
export default function useTheme() {
return { theme }
}同时,Home.vue 中的计算属性已正确使用 computed,无需修改:
Element Plus Vue3 桌面组件库。在开发 Vue 3.x 页面时调用,使用 Element Plus 组件(如 el-button、el-input、el-form、el-table 等)。
<!-- Home.vue -->
<script setup>
import { computed } from 'vue'
import { useTheme } from '@/composables/useTheme'
import DayTimeBanner from '@/assets/banner-city-daytime.png'
import NightTimeBanner from '@/assets/banner-city-nighttime.png'
const { theme } = useTheme()
const banner = computed(() =>
theme.value ? NightTimeBanner : DayTimeBanner
)
</script>
<template>
<img :src="banner" :alt="theme.value ? 'Nighttime Banner' : 'Daytime Banner'" />
</template>⚠️ 注意事项:
立即学习“前端免费学习笔记(深入)”;
- 不要在
useTheme内部重复写localStorage.setItem(如App.vue中的显式设置),否则会与watchEffect冲突,造成冗余或竞态; - 确保
banner是computed而非普通ref或let变量,否则不会建立响应式依赖; - 若项目使用 TypeScript,建议为
theme显式标注类型:const theme = ref<boolean>(...)</boolean>; - 初次加载时,
localStorage中值为'false'(字符串)会被JSON.parse解析为布尔false,逻辑正确;但若存储格式不一致(如存为'{"dark":true}'),需调整解析逻辑。
总结:Vue 的响应式系统依赖于响应式数据的单一实例性。自定义 Hook 不是“状态工厂”,而是“状态访问器”——其核心职责是暴露已存在的、跨组件共享的响应式源。将 ref 提升至模块级,是实现真正状态共享的基石。

















