用Hook改造React文档的五子棋游戏

393 阅读4分钟

这两天在学习react,粗略过了一边文档之后想说用react练练手,然后看到react文档里面有个关于五子棋的小游戏,于是跟着步骤写了一边,由于教程是class语法写的,然后想说既然hook这么方便,那咱们用hook改造一下来对比看哪种更简洁呢hhh。

声明,改文章不适合react纯小白阅读,可以提前查阅react文档了解部分语法之后再来阅读哈

game.gif (游戏示例)

以上是游戏的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.png

相信看到上面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>
  )
}

game.gif

如上,就实现了点击修改值的功能,但此时我们并不知道下一个落子的是哪方,所以咱们创建一个新的变量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>
  )
}

2.gif

可以看到我在最后点击的时候有个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还是不错的,如果觉得我写的不错可以给我点个赞鼓励一下哈

美女镇楼

微信图片_20211217105829.jpg