初识React(一)从井字棋游戏开始

本文讲述了作者学习React过程中,通过创建井字棋游戏实例,介绍了jsx语法、函数式编程、props传递、useStateHook、列表渲染、事件处理等内容,展示了React与Vue的不同之处。
摘要由CSDN通过智能技术生成

写在前面:

磨磨唧唧了好久终于下定决心开始学react,刚刚接触感觉有点无从下脚...新的语法新的格式跟vue就像两种物种...倒是很好奇路由和store是怎么实现的了~v~,一点一点来吧!!!

(一)创建项目

使用vite创建

npm create vite

选择react项目,欧啦现在开始:

main.tsx文件:

还是熟悉的味道,鉴于我还搞不清楚组件和路由,就直接在app.tsx里面开干

(二)制作井字棋游戏 

1. 搭建九宫格形状

(1)用Square组件实现小格子

function Square() {
    return (
      <>
        <button className='square'>x</button>
      </>
    )
  }

(2)使用数组map方法渲染出九个Square组件

不同于vue的v-for,react使用数组方法实现列表的渲染

function Board() {
    // 存储每个格子的值 初始化为空
    const squares = new Array(9).fill('')
    const listSquare = squares.map((value, index) => (
      <Square key={index} />
    ))

    return (
      <>
        {listSquare}
      </>
    )
  }

(3)通过props传递数据

将square组件的值存储在Board里面,通过props传值

function Square({ content }) {
    return (
      <>
        <button className='square'>{content}</button>
      </>
    )
  }
function Board() {
    ...
    const listSquare = squares.map((value, index) => (
      <Square key={index} content={value} />
    ))
    ...
  }

这样的props传值感觉更清晰,子组件可以接收到props对象,展开的content变量直接就用

再提一嘴react的这个单括号:既可以像vue的插值语法{{}},也像v-bind数据绑定一样传递数据,蛮有意思蛮有意思

2.实现交互效果

(1)点击square显示×号

在sqare组件中实现:

使用useState钩子来实现对数据的记忆和更改,类似于vue的响应式数据

const [content, setContent] = useState('')中:content相当于数据的getter,setContent相当于数据的setter 

function Square() {
    const [content, setContent] = useState('')
    function handleClick() {
      setContent('X')
    }
    return (
      <>
        <button className='square' onClick={handleClick}>{content}</button>
      </>
    )
  }

这样虽然实现了点击就改变square的内容,不过改变后的结果Board组件并不能得知 

(2)状态提升

数据存储在square中是无法实现完成O和X的交替点击,以及判定赢家等操作的

因此将点击事件和state存储在父组件Board上,通过props传递给各个square组件即可

function Square({ content, onSquareClick }) {
    return (
      <>
        <button className='square' onClick={onSquareClick}>{content}</button>
      </>
    )
  }
function Board() {
    // 存储每个格子的值 初始化为空
    const [squares, setSquares] = useState(new Array(9).fill(''))
    // 点击对应索引的square,更新数组
    function handleClick(index) {
      const nextSquares = squares.slice()
      nextSquares[index] = 'X'
      setSquares(nextSquares)
    }
    // 遍历渲染九个square组件
    const listSquare = squares.map((value, index) => (
      <Square key={index} content={value} onSquareClick={() => { handleClick(index) }} />
    ))
    ...
  }

注意:不能直接 onSquareClick={ handleClick(index) } 将函数传给Square组件,因为这样会直接调用点击事件更新state,造成死循环;

可以通过套一层函数调用handleClick解决,因此最便捷的就是直接使用箭头函数调用

(3)不变性

为什么在handleClick函数中要使用slice()复制一个新数组,对其进行更改后再setState?

为了维持不变性,如果直接对state进行修改,当父组件的state改变后,所有的子组件都会跟着重新渲染(包括未受影响的子组件)

不行...我只觉得直接改变state的话那setState的意义在哪里...?为什么要遵守不变性我这个例子没办法充分证明,后续遇到了再看看

(4)交替传值

直接用一个state来保存当前应该填充的内容

// 记录本次点击内容是O还是X
const [isX, setIsX] = useState(true)
// 点击对应索引的square,更新数组
function handleClick(index) {
    // 如果square内容已经被填充为o或者x 就不进行后续操作
    if (squares[index] !== '') return

    const nextSquares = squares.slice()
    if (isX) {
        nextSquares[index] = 'X'
    } else {
        nextSquares[index] = 'O'
    }
    setSquares(nextSquares)
    setIsX(!isX)
}

(5)判定赢家 

判断是否胜利的逻辑就直接copy了,就是能够胜利的情况的数组集合

// 判断是否胜利
function calculateWinner(squares) {
      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 (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
          return squares[a];
        }
      }
      return null;
    }

使用变量winner存储结果

const winner = calculateWinner(squares)

如果winner有值了就不能触发点击事件

function handleClick(index) {
      // 如果square内容已经被填充为o或者x或者游戏结束,就不进行后续操作
      if (squares[index] !== '' || calculateWinner(squares)) return
      ...
}

写上提示,主题功能就完成了!

function Board() {
    ...
    const winner = calculateWinner(squares)

    return (
      <>
        <div className="content">
          {/* 判断是正在游戏还是有赢家了 */}
          {winner ? (
            <p className='player'>赢家:{winner}</p>
          ) : (
            <p className='player'>本轮玩家:{isX ? 'X' : 'O'}</p>
          )}

          <div className='board'>
            {listSquare}
          </div>
        </div>
      </>
    )
  }

注释都要包大括号,蛮诡异的^-^

3.全部代码

其实还有个历史回退功能的,懒得写了,这个入门教程已经大致涵盖了react的基础特色; 

function App() {

  function Square({ content, onSquareClick }) {

    return (
      <>
        <button className='square' onClick={onSquareClick}>{content}</button>
      </>
    )
  }

  function Board() {
    // 存储每个格子的值 初始化为空
    const [squares, setSquares] = useState(new Array(9).fill(''))
    // 记录本次点击内容是O还是X
    const [isX, setIsX] = useState(true)

    // 点击对应索引的square,更新数组
    function handleClick(index) {
      // 如果square内容已经被填充为o或者x或者游戏结束,就不进行后续操作
      if (squares[index] !== '' || calculateWinner(squares)) return

      const nextSquares = squares.slice()
      if (isX) {
        nextSquares[index] = 'X'
      } else {
        nextSquares[index] = 'O'
      }
      setSquares(nextSquares)
      setIsX(!isX)
    }
    // 遍历渲染九个square组件
    const listSquare = squares.map((value, index) => (
      <Square key={index} content={value} onSquareClick={() => { handleClick(index) }} />
    ))

    // 判断是否胜利
    function calculateWinner(squares) {
      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 (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
          return squares[a];
        }
      }
      return null;
    }

    const winner = calculateWinner(squares)

    return (
      <>
        <div className="content">
          {/* 判断是正在游戏还是有赢家了 */}
          {winner ? (
            <p className='player'>赢家:{winner}</p>
          ) : (
            <p className='player'>本轮玩家:{isX ? 'X' : 'O'}</p>
          )}

          <div className='board'>
            {listSquare}
          </div>
        </div>
      </>
    )
  }

  return (
    <>
      <Board />
    </>
  )
}

(三)总结

已经学过一门框架了,再看react的话只是思维不太一样,在react里也能看到蛮多vue仿鉴的东西,所以最开始的基础就不像以前一样写的又慢又细了

这个入门井字棋游戏概括了很多的基础知识

1.jsx的函数式编程

2.父子组件的props传递

3.列表的渲染

4.useState HOOK

5.条件渲染

6.响应事件

速度速度,再不学学不完了QAQ

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值