1. 项目背景与挑战
在一个 Vue 3 + Composition API 的五子棋项目中,重复代码主要来自于 棋盘渲染、落子处理、胜负判定 等模块。
随着功能扩展与界面迭代,重复逻辑越来越多,维护成本明显上升,尤其在不同棋盘尺寸、棋子样式和 AI 玩法切换时,重复模式会放大。
本节围绕 temperature=0.6 的场景,展示如何通过实战重构与组件化,系统性地消除重复代码、提升可维护性,并为后续迭代留出空间。
2. 识别重复代码的模式
常见重复点
在五子棋项目中,棋盘格的渲染逻辑、落子按钮的事件处理、以及棋子状态的更新往往在不同组件中重复出现。
此外,胜负判定算法、轮到谁下、棋局快照与撤销重做等功能经常以相似的方式被重复实现。
通过对这些模式进行抽象,可以将重复点提炼为 可重用的通用组件与工具函数,以实现统一的行为和 UI 风格。
重构的优先级与风险评估
优先级应聚焦于那些改变成本低、复用性高的模块,比如棋盘渲染和事件通信的组件化,以及通用的棋局状态管理。

在进行重构时,需要评估风险,确保在引入组件化后,现有功能保持不变且可测试性提升,避免引入未覆盖的边界情况。
一个可衡量的目标是:通过一次抽象,尽量减少重复的逻辑行数,并在基础用例上保持行为一致。
3. 组件化设计框架
核心组件划分
实现一个清晰的组件边界,可以显著降低耦合度,将棋盘、棋子、以及棋局控制逻辑分离,同时为后续扩展留出空间。
建议的核心组件包括:BoardBoard(棋盘容器)、Cell(单元格/格子)、Stone(棋子显示)、Controls(控件区),以及一个 useGame 的组合式 API(composable)来管理状态与逻辑。
通过将 UI 与业务逻辑分离,不同棋盘尺寸和 UI 主题可以复用同一套逻辑,仅替换数据源即可。
数据流与事件协作
采用 自上而下的数据流,父组件通过 props 传入棋盘数据,子组件通过事件向父组件汇报落子意图,父组件再驱动状态变更。
在这套模式中,事件命名应语义化、接口稳定,方便未来用 Pinia/Vuex 的全局状态管理替代局部状态,进一步提升可维护性。
此外,结合 Slots 提供可定制的棋子样式与格子渲染,可以实现高度灵活的 UI 主题切换而不改变底层逻辑。
4. 实战重构步骤与代码示例
4.1 抽取棋盘渲染组件
将棋盘的渲染独立成一个 Board 组件,负责格子的布局与棋子的显示,统一处理格子点击事件,从而消除各处重复的渲染代码。
以下示例展示一个简化的 Board 组件结构,包含了棋盘尺寸、棋子数据与点击回调的暴露,以及对空格的渲染逻辑。
// Board.vue
<script setup lang="ts">
import { defineProps, computed } from 'vue'
type Player = 1 | -1 | 0
const props = defineProps<{ size: number; cells: Player[][]; onPlay: (x:number,y:number)=>void }>()const onCellClick = (x:number, y:number) => {if (props.cells[y][x] !== 0) returnprops.onPlay(x, y)
}
</script><template><div class="board" :style="{ gridTemplateColumns: `repeat(${props.size}, 1fr)` }"><!-- 循环渲染每个格子 --><span v-for="(row, y) in props.cells" :key="y" v-for="(cell, x) in row" :key="x" class="cell" @click="onCellClick(x, y)"><Stone :value="cell" /></span></div>
</template>
在这段代码中,Board.vue 只关注渲染与事件传递,具体棋子的落子逻辑交给上层组合式 API 处理,以减少重复实现。
4.2 封装落子与胜负逻辑
将落子、胜负判定等业务逻辑封装成一个 useGame 的组合式函数,提供统一的 API,包括下棋、撤销、重新开始和胜负检查等能力。
这样做的核心价值是:业务逻辑在一个地方集中维护,页面组件只需要调用接口即可,进一步降低重复实现的风险。
// useGame.ts
import { ref } from 'vue'export function useGame(size = 15) {const board = ref(Array.from({ length: size }, () => Array(size).fill(0)))const current = ref<1 | -1>(1)const winner = ref<0 | 1 | -1 | null>(null)const place = (x:number, y:number) => {if (board.value[y][x] !== 0 || winner.value !== null) return falseboard.value[y][x] = current.valueif (checkWin(x, y)) {winner.value = current.value} else {current.value = -current.value}return true}const checkWin = (x:number, y:number) => {// 简化示例:实际应实现横竖斜四方向计数// 仅作为结构演示return false}const reset = () => {board.value = Array.from({ length: size }, () => Array(size).fill(0))current.value = 1winner.value = null}return { board, current, winner, place, reset, checkWin }
}
在上例中,useGame 提供统一的棋局状态与落子/胜负逻辑,Board 组件只关注渲染与事件分发,降低重复度并提升复用性。
4.3 走棋逻辑的可复用性与单元测试
为了确保重构后的代码稳定,需要对走棋逻辑进行单元测试,尽量将落子、撤销、胜负判断等核心逻辑独立起来,便于在不同组件之间重复使用。
下面给出一个简单的单元测试示例,聚焦于 下子逻辑与胜负判定的边界场景,以验证组合式 API 的正确性。
// useGame.test.ts
import { describe, it, expect } from 'vitest'
import { useGame } from './useGame'describe('useGame', () => {it('should place a stone and switch player', () => {const { board, current, place } = useGame(3)place(0, 0)expect(board.value[0][0]).toBe(1)expect(current.value).toBe(-1)})
})
5. 性能与可维护性优化
组件复用与性能优化
通过组件化设计,避免重复渲染与重复逻辑计算,尤其在同尺寸棋盘多处复用时,渲染与事件处理只需一次实现即可。
借助 Vue 的响应式系统,只在数据发生实际变化时触发更新,配合 computed 与 watch,可以减少不必要的渲染开销。
测试驱动与持续重构
采用 测试驱动开发(TDD) 的方式,在每次重构后运行自动化测试,确保行为一致性,并用 持续集成 监控回归。
持续将通用逻辑迁移到 组合式 API 与工具函数,让 UI 层只负责展示,直观地看见变更对不同页面的影响,从而提高维护性。
6. 调参与实战经验
6.1 在 AI 对局中温度参数的作用
在 AI 局的实现中,可以利用 temperature=0.6 作为采样温度,对候选落子进行概率采样,以实现适度的随机性与探索性,提升对局体验的多样性。
将 温度参数与可复用的走棋逻辑分离,便于在不同难度和玩法之间快速切换,而不需要改动核心的棋盘渲染与基本规则。
6.2 如何通过组件化降低耦合以便调参
组件化设计让 AI 与 UI 的耦合降至最低,将策略实现独立成可插拔的策略组件,在不改变棋盘和落子事件的前提下移植或替换不同策略。
通过将策略参数曝露为 props 或 Composition API 的响应式变量,实现对调参的更快迭代与回滚,提升对照测试的效率。
总体而言,针对 temperature=0.6 在 Vue 五子棋项目中的应用场景,通过系统的组件化、统一的业务逻辑抽象,以及可测试的组合式 API,可以高效地消除重复代码、提升可维护性,并为后续扩展建立稳固的代码基础。


