
本文介绍将传统 JS 逻辑封装为 Vue 组合式函数(composable)的正确方式,通过 ref 管理状态并利用 watch 实现响应式监听,解决无法监听外部模块普通导出变量的问题。
本文介绍将传统 js 逻辑封装为 vue 组合式函数(composable)的正确方式,通过 `ref` 管理状态并利用 `watch` 实现响应式监听,解决无法监听外部模块普通导出变量的问题。
在 Vue 3 的组合式 API 中,watch() 只能有效监听响应式源(如 ref、reactive 对象的属性、或返回响应式值的 getter 函数)。而你原始代码中 game.js 导出的 timeRemaining 是一个普通数字变量(export let timeRemaining = 30),其修改(如 timeRemaining--)不会触发 Vue 的响应式系统,因此 watch(game.timeRemaining, ...) 和 ref(game.timeRemaining) 均无效——前者监听的是初始值的快照,后者仅复制初始值,后续变更完全不可见。
✅ 正确解法是:将游戏状态与逻辑迁移至符合 Vue 响应式规范的组合式函数中,即创建 useGame() composable:
// composables/useGame.js
import { ref, onUnmounted } from 'vue';
export default function useGame() {
const timeRemaining = ref(30);
const score = ref(0);
let timer = null;
const startGame = () => {
timer = setInterval(() => {
timeRemaining.value--;
if (timeRemaining.value {
try {
// 示例:调用 Laravel 后端接口
await fetch('/api/submit-score', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ score: score.value, time: timeRemaining.value })
});
} catch (err) {
console.error('Failed to submit score:', err);
}
};
const init = () => {
// 重置状态
timeRemaining.value = 30;
score.value = 0;
startGame();
};
// 自动清理定时器(推荐)
onUnmounted(() => {
if (timer) clearInterval(timer);
});
return {
timeRemaining,
score,
init,
// 如需外部控制,可暴露方法
addScore: (points) => { score.value += points; }
};
}
在组件中使用时,直接解构响应式引用,并可自由 watch 或绑定到模板:
<!-- Game.vue -->
<script setup>
import { watch } from 'vue';
import useGame from '@/composables/useGame';
const { timeRemaining, score, init } = useGame();
// 监听倒计时归零
watch(timeRemaining, (newVal) => {
if (newVal === 0) {
console.log('Game over! Final score:', score.value);
}
});
// 启动游戏(也可放在 composable 内部自动执行)
init();
</script><template><div class="game-ui">
<h2>Time remaining: {{ timeRemaining }}s</h2>
<p>Current score: {{ score }}</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6494" title="Feishu calendar sync, local ics to json data for AI agent"><img
src="https://img.php.cn/upload/skill/000/000/081/179099469998654.jpg" alt="Feishu calendar sync, local ics to json data for AI agent" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6494" title="Feishu calendar sync, local ics to json data for AI agent" class="overflowclass">Feishu calendar sync, local ics to json data for AI agent</a>
<p class="overflowclass">将ICS日历文件转为JSON格式,用于飞书日历导入导出及数据集成。</p>
</div>
<a rel="nofollow" href="/xiazai/skill6494" title="Feishu calendar sync, local ics to json data for AI agent" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<!-- 其他游戏 UI -->
</div>
</template>
⚠️ 注意事项:
- ❌ 不要尝试对普通 JS 模块变量做
ref(x)或watch(x, ...)—— 这无法建立响应式连接; - ✅ 所有需响应式更新的状态(
timeRemaining、score)必须由ref()或reactive()创建,并统一在 composable 内管理; - ✅ 定时器等副作用建议在 composable 中用
onUnmounted清理,避免内存泄漏; - ✅ 若需与 Laravel 后端通信,确保 CSRF Token 已正确配置(Laravel 默认要求),可在
fetch请求头中添加X-XSRF-TOKEN或使用 Axios 自动处理。
通过此方案,你既保留了原有游戏逻辑的可维护性,又完全融入 Vue 的响应式生态,轻松实现数据驱动视图、监听状态变化及对接后端接口。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










