
本文介绍如何将传统非响应式的外部游戏逻辑(如 game.js)改造为 Vue 3 可响应式监听的组合式函数(composable),解决 watch 无法监听普通导出变量的问题,并实现倒计时归零时自动提交分数到 Laravel 后端。
本文介绍如何将传统非响应式的外部游戏逻辑(如 `game.js`)改造为 vue 3 可响应式监听的组合式函数(composable),解决 `watch` 无法监听普通导出变量的问题,并实现倒计时归零时自动提交分数到 laravel 后端。
在 Vue 3 中,watch() 只能响应式地追踪 响应式数据源(如 ref、reactive 或其属性),而你原始代码中 export let timeRemaining = 30 导出的是一个普通 JavaScript 基础类型值,它不具备响应式能力。因此,直接 watch(game.timeRemaining) 或 ref(game.timeRemaining) 都无法触发更新——因为 Vue 无法感知该变量后续在 game.js 内部被赋值(如 timeRemaining--)的变化。
✅ 正确做法是:将游戏状态和逻辑封装为 Vue Composable(组合式函数),用 ref 管理状态,并暴露可控的响应式接口。
✅ 推荐方案:创建 useGame 组合式函数
新建文件 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 = () => {
// 清除可能残留的定时器(防重复启动)
if (timer) clearInterval(timer);
timer = setInterval(() => {
timeRemaining.value--;
console.log(`Time remaining: ${timeRemaining.value}`);
// 倒计时结束时自动触发处理逻辑
if (timeRemaining.value {
try {
// 示例:使用 fetch 提交至 Laravel 后端
await fetch('/api/submit-score', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ score: score.value, time: timeRemaining.value })
});
console.log('Score submitted successfully');
} catch (err) {
console.error('Failed to submit score:', err);
}
};
const init = () => {
// 可在此初始化其他游戏逻辑(如洗牌、重置状态等)
score.value = 0;
timeRemaining.value = 30;
startGame();
};
// 自动清理定时器(组件卸载时)
onUnmounted(() => {
if (timer) clearInterval(timer);
});
return {
timeRemaining,
score,
init,
submitScore // 如需手动触发,可暴露此方法
};
}
✅ 在 .vue 组件中使用
<script setup>
import { onMounted, watch } from 'vue';
import useGame from '@/composables/useGame'; // 注意路径正确性
const { timeRemaining, score, init } = useGame();
// ✅ 响应式监听倒计时变化
watch(timeRemaining, (newVal) => {
if (newVal === 0) {
console.log('Game over — score will be auto-submitted');
}
});
// ✅ 组件挂载后启动游戏
onMounted(() => {
init();
});
</script><template><div class="game-ui">
<h2>Memory Game</h2>
<p>⏱️ Time remaining: {{ timeRemaining }}s</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill4104" title="Miller CSV TSV JSON 数据处理器"><img
src="https://img.php.cn/upload/skill/000/000/081/178990562819390.jpg" alt="Miller CSV TSV JSON 数据处理器" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill4104" title="Miller CSV TSV JSON 数据处理器" class="overflowclass">Miller CSV TSV JSON 数据处理器</a>
<p class="overflowclass">Miller (mlr) 是一个命令行工具,用于查询、整形和重新格式化名称索引数据,如 CSV、TSV、JSON 和 JSON Lines。它将 awk、sed、cut、join 和 sort 的功能整合到一个专为结构化数据处理而构建的单一工具中。</p>
</div>
<a rel="nofollow" href="/xiazai/skill4104" title="Miller CSV TSV JSON 数据处理器" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<p>? Score: {{ score }}</p>
<!-- 其他游戏 UI -->
</div>
</template>
⚠️ 关键注意事项
- ❌ 不要再尝试
ref(game.timeRemaining)或watch(game.timeRemaining)—— 普通变量无响应性; - ✅ 所有状态必须由
ref()或reactive()创建,并通过.value访问/修改; - ✅ 定时器需手动
clearInterval,推荐配合onUnmounted防止内存泄漏; - ✅ 若需与 Laravel 后端通信,请确保 CSRF Token 已正确配置(Laravel 默认要求),例如在
app.blade.php中设置:<meta name="csrf-token" content="{{ csrf_token() }}">并在
fetch请求头中添加:headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type': 'application/json' }
通过该方式,你既保留了原有游戏逻辑的模块化结构,又完全融入 Vue 的响应式系统,轻松实现状态同步、副作用监听与后端交互,真正践行“逻辑复用 + 响应式驱动”的现代 Vue 开发范式。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










