这两天在学习react,粗略过了一边文档之后想说用react练练手,然后看到react文档里面有个关于五子棋的小游戏,于是跟着步骤写了一边,由于教程是class语法写的,然后想说既然hook这么方便,那咱们用hook改造一下来对比看哪种更简洁呢hhh。
声明,改文章不适合react纯小白阅读,可以提前查阅react文档了解部分语法之后再来阅读哈
(游戏示例)
以上是游戏的gif,可以看出该五子棋是由9个按钮组成的棋盘,然后通过点击事件来进行X和O的轮流落子,最后通过坐标判断胜利者。话不多数,咱们开始吧!
创建棋盘
众所周知,react是通过函数式组件来进行编程。环境啥的咱也不多说了,我是直接通过脚手架来创建的。此时我们创建一个Board.js文件,然后创建Btn函数生成棋盘里的小格子。
// btn小格子
function Btn() {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
)
}
接着创建棋盘组件,咱们取名Board,代码如下:
function Board() {
return (
<div className="board">
<div className="board-row">
<Btn></Btn>
<Btn></Btn>
<Btn></Btn>
<Btn></Btn>
<Btn></Btn>
<Btn></Btn>
<Btn></Btn>
<Btn></Btn>
<Btn></Btn>
</div>
</div>
)
}
// 然后把组件暴露出去渲染
export default Board
此时页面如下:(css我就不多说了,相信对各位吴彦祖来说是洒洒水的)
相信看到上面Board函数的吴彦祖肯定会说啥玩意啊,写九个Btn,不会循环吗?别急啊,咱们一步一步来。没错,同样的方法写九个太冗余了,我们来用for循环改造一下,
function Board() {
const btnItem = Array(9)
.fill(null)
.map((item, index) => {
//别忘了key props传值
return <Btn value={item} key={index}></Btn>
})
return (
<div className="board">
<div className="board-row">{btnItem}</div>
</div>
)
}
然后Btn函数接受父组件的props
function Btn(props) {
return (
<button className="square">
{props.value}
</button>
)
}
添加点击事件实现轮流落子
这个时候我们发现棋盘上是空白的,那我们需要让棋盘展示数据呀。怎么办呢?还记得我们已经通过props传值了吗,现在可以添加事件来修改值的内容了,也就用到咱们题目所说的Hook了
function Board() {
const [list, setList] = useState(Array(9).fill(null)) //棋盘数量
// 点击修改list对应索引的值
const renderBtn = (i) => {
const arr = [...list]
arr[i] = i % 2 ? 'O' : 'X'
setList(arr)
}
const btnItem = list.map((item, index) => {
return (
<Btn
value={index}
key={index}
onClick={() => {
//是一个回调函数,因为业务冗余所以抽离出来
renderBtn(index)
}}
></Btn>
)
})
return (
<div className="board">
<div className="board-row">{btnItem}</div>
</div>
)
}
如上,就实现了点击修改值的功能,但此时我们并不知道下一个落子的是哪方,所以咱们创建一个新的变量isNext,当然也是用hook->useState实现
function Board() {
const [list, setList] = useState(Array(9).fill(null)) //棋盘数量
const [isNext, setIsNext] = useState(false) //用来判断下一步落子方 默认X为第一手
const renderBtn = (i) => {
const arr = [...list]
arr[i] = isNext ? 'O' : 'X'
setList(arr)
setIsNext(!isNext)
}
const btnItem = list.map((item, index) => {
return (
<Btn
value={item}
key={index}
onClick={() => {
renderBtn(index)
}}
></Btn>
)
})
return (
<div className="board">
<div className="status">下一步为:{isNext ? 'O' : 'X'}</div>
<div className="board-row">{btnItem}</div>
</div>
)
}
可以看到我在最后点击的时候有个bug,如果已经填入了值则不允许再次点击,所以咱们在renderBtn中加个判断
const renderBtn = (i) => {
const arr = [...list]
if (arr[i]) return //禁止重复输入
arr[i] = isNext ? 'O' : 'X'
setList(arr)
setIsNext(!isNext)
}
判断胜者
到这里呢咱们的功能已经实现的差不多了,但是游戏吗,必须分出胜利的一方!!!这里的思路呢,我其实是直接抄的官网的hhh,偷懒了抱歉。其实就是把每条取胜的线路都找出来,然后跟我们落子的数组进行判断,如果落子的数组索引和取胜线路一致则取胜,代码如下:
function calculateWinner(btns) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
]
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i]
//判断三个棋子和线路匹配
if (btns[a] && btns[a] === btns[b] && btns[a] === btns[c]) {
return btns[a]
}
}
return null
}
最后我们再次修改一下Board函数
function Board() {
const [list, setList] = useState(Array(9).fill(null)) //棋盘数量
const [isNext, setIsNext] = useState(false) //用来判断下一步落子方 默认X为第一手
const [winner, setWinner] = useState('')
const renderBtn = (i) => {
const arr = [...list]
if (calculateWinner(arr) || arr[i]) return
arr[i] = isNext ? 'O' : 'X'
setList(arr)
if (calculateWinner(arr)) {
return setWinner(calculateWinner(arr))
}
setIsNext(!isNext)
}
const btnItem = list.map((item, index) => {
return (
<Btn
value={item}
key={index}
onClick={() => {
renderBtn(index)
}}
></Btn>
)
})
return (
<div className="board">
<div className="status">{winner ? `胜利者是:${winner}` : `下一步为:${isNext ? 'O' : 'X'}`}</div>
<div className="board-row">{btnItem}</div>
</div>
)
}
可以看到,我又添加了一个变量,这个变量是用来判断是否取胜,取胜的话就展示胜利者是xxx的文案,否则就展示落子文案。
到这里,咱们的game就结束了,当然官网还有回退的功能我还没做,但是以上的功能能用来熟悉一下hook还是不错的,如果觉得我写的不错可以给我点个赞鼓励一下哈
美女镇楼