最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React中井字棋游戲的實(shí)現(xiàn)示例

 更新時(shí)間:2022年08月05日 10:33:57   作者:草帽Plasticine?lv-4  
本文主要介紹了React中井字棋游戲的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

最近開(kāi)始接觸React,我認(rèn)為讀官方文檔是最快上手一門技術(shù)的途徑了,恰好React的官方文檔中有這樣一個(gè)井字棋游戲demo,學(xué)習(xí)完后能夠快速上手React,這是我學(xué)習(xí)該demo的總結(jié)

需求分析

首先看看這個(gè)游戲都有哪些需求吧

  • 游戲玩家:XO,每次落棋后需要切換到下一個(gè)玩家
  • 贏家判斷:什么情況下會(huì)誕生贏家,如何進(jìn)行判斷?
  • 禁止落棋的時(shí)機(jī):游戲已有贏家 or 棋盤上已有棋子時(shí)
  • 時(shí)間旅行:能夠展示游戲下棋歷史,點(diǎn)擊可跳轉(zhuǎn)回相應(yīng)的棋局

實(shí)現(xiàn)分析

首先聲明一下,我不會(huì)像官方文檔那樣一步步從底層實(shí)現(xiàn),然后逐步狀態(tài)提升至父組件的方式講解,而是直接從全局分析,分析涉及哪些狀態(tài),應(yīng)當(dāng)由哪個(gè)組件管理以及這樣做的原因是什么

涉及的組件

先來(lái)思考一下整個(gè)游戲會(huì)涉及什么組件:

  • 首先最基本的,打開(kāi)游戲最能吸引目光的,就是棋盤了,所以肯定得有一個(gè)棋盤組件Board
  • 棋盤有多個(gè)格子,因此還能將棋盤分割成多個(gè)格子組件Square
  • 還需要有一個(gè)游戲界面去控制游戲的UI以及游戲的邏輯,所以要有一個(gè)Game組件

涉及的狀態(tài)

  • 棋盤中的每個(gè)格子的棋子是什么,比如是X還是O
  • 下一步是哪個(gè)玩家
  • 棋盤的歷史記錄,每下一步棋都要保存整個(gè)棋盤的狀態(tài)
  • 棋盤歷史記錄指針,控制當(dāng)前的棋盤是歷史記錄中的哪個(gè)時(shí)候的棋盤

我們可以自頂向下分析,最頂層的狀態(tài)肯定是歷史記錄,因?yàn)樗锩姹4嬷恳徊降钠灞P,而棋盤本應(yīng)該作為Board組件的狀態(tài)的,但又由于有多個(gè)變動(dòng)的棋盤(用戶點(diǎn)擊歷史記錄切換棋盤時(shí)),所以不適合作為state放到Board組件中,而應(yīng)當(dāng)作為props,由父組件Game去控制當(dāng)前展示的棋盤

而棋盤中的格子又是在棋盤中的,所以也導(dǎo)致本應(yīng)該由棋盤格子Square組件管理的格子內(nèi)容狀態(tài)提升至Game組件管理,存放在歷史記錄的每個(gè)棋盤對(duì)象中,所以Square的棋盤內(nèi)容也應(yīng)當(dāng)以props的形式存在

下一步輪到哪個(gè)玩家是視棋盤的情況而定的,所以我認(rèn)為應(yīng)當(dāng)放到歷史記錄的棋盤對(duì)象里和棋盤一起進(jìn)行管理,官方那種放到Gamestate中而不是放到歷史記錄的每個(gè)棋盤中的做法我覺(jué)得不太合適

有了以上的分析,我們就可以開(kāi)始寫我們的井字棋游戲了!

編碼實(shí)現(xiàn)

項(xiàng)目初始化

首先使用vite創(chuàng)建一個(gè)react項(xiàng)目

pnpm create vite react-tic-tac-toe --template react-ts
cd react-tic-tac-toe
pnpm i
code .

這里我使用vscode進(jìn)行開(kāi)發(fā),當(dāng)然,你也可以使用別的ide(如Neovim、WebStorm

定義各個(gè)組件的props/state

由于使用的是ts進(jìn)行開(kāi)發(fā),所以我們可以在真正寫代碼前先明確一下每個(gè)組件的propsstate,一方面能夠讓自己理清一下各個(gè)組件的關(guān)系,另一方面也可以為之后編寫代碼提供一個(gè)良好的類型提示

Square組件props

每個(gè)棋盤格中需要放棋子,這里我使用字符XO充當(dāng)棋子,當(dāng)然,棋盤上也可以不放棋子,所以設(shè)置一個(gè)squareContent屬性

點(diǎn)擊每個(gè)格子就是落棋操作,也就是要填充一個(gè)字符到格子中,根據(jù)前面的分析我們知道,填充的邏輯應(yīng)當(dāng)交由棋盤Board組件處理,所以再添加一個(gè)onFillSquareprop,它起到一個(gè)類似事件通知的作用,當(dāng)調(diào)用這個(gè)函數(shù)的時(shí)候,會(huì)調(diào)用父組件傳入的函數(shù),起到一個(gè)通知的作用

所以Square組件的props接口定義如下:

interface Props {
  squareContent: string | null;
  fillSquare: () => void;
}

Board組件props

棋盤中要管理多個(gè)格子,所以肯定要有一個(gè)squares狀態(tài),用于控制各個(gè)格子

棋盤填充棋子的邏輯也應(yīng)當(dāng)交給Game組件去完成,因?yàn)橐S護(hù)歷史記錄,而棋盤的狀態(tài)都是保存在歷史記錄中的,所以填充棋子也要作為Board組件的一個(gè)prop

還要在棋盤上顯示下一個(gè)玩家以及在對(duì)局結(jié)束時(shí)顯示贏家信息,所以要有一個(gè)statusMsgprop顯示對(duì)局信息,以及nextPlayer記錄下一個(gè)玩家

最終Board組件的props接口定義如下:

interface Props {
  squares: Squares;
  statusMsg: string;
  nextPlayer: Player;
  fillSquare: (squareIdx: number) => void;
}

Game組件state

要記錄歷史信息,以及通過(guò)歷史記錄下標(biāo)獲取到對(duì)應(yīng)歷史記錄的棋盤,所以它的State如下

interface State {
  history: BoardPropsNeeded[];
  historyIdx: number;
}

各組件代碼

Square

export interface Props {
  squareContent: string | null;
  fillSquare: () => void;
}

export type Squares = Omit<Props, "fillSquare">[];

export default function Square(props: Props) {
  return (
    <div className="square" onClick={() => props.fillSquare()}>
      {props.squareContent}
    </div>
  );
}

Board

import React from "react";
import Square from "./Square";
import type { Squares } from "./Square";

export type Player = "X" | "O";

export interface Props {
  squares: Squares;
  statusMsg: string;
  nextPlayer: Player;
  fillSquare: (squareIdx: number) => void;
}

export default class Board extends React.Component<Props> {
  renderSquare(squareIdx: number) {
    const { squareContent } = this.props.squares[squareIdx];

    return (
      <Square
        squareContent={squareContent}
        fillSquare={() => this.props.fillSquare(squareIdx)}
      />
    );
  }

  render(): React.ReactNode {
    return (
      <div>
        <h1 className="board-status-msg">{this.props.statusMsg}</h1>
        <div className="board-row">
          {this.renderSquare(0)}
          {this.renderSquare(1)}
          {this.renderSquare(2)}
        </div>
        <div className="board-row">
          {this.renderSquare(3)}
          {this.renderSquare(4)}
          {this.renderSquare(5)}
        </div>
        <div className="board-row">
          {this.renderSquare(6)}
          {this.renderSquare(7)}
          {this.renderSquare(8)}
        </div>
      </div>
    );
  }
}

Game

import React from "react";
import Board from "./Board";
import type { Props as BoardProps, Player } from "./Board";
import type { Squares } from "./Square";

type BoardPropsNeeded = Omit<BoardProps, "fillSquare">;

interface State {
  history: BoardPropsNeeded[];
  historyIdx: number;
}

export default class Game extends React.Component<any, State> {
  constructor(props: any) {
    super(props);

    this.state = {
      history: [
        {
          squares: new Array(9).fill({ squareContent: null }),
          nextPlayer: "X",
          statusMsg: "Next player: X",
        },
      ],
      historyIdx: 0,
    };
  }

  togglePlayer(): Player {
    const currentBoard = this.state.history[this.state.historyIdx];
    return currentBoard.nextPlayer === "X" ? "O" : "X";
  }

  fillSquare(squareIdx: number) {
    const history = this.state.history.slice(0, this.state.historyIdx + 1);
    const currentBoard = history[this.state.historyIdx];
    // 先判斷一下對(duì)局是否結(jié)束 結(jié)束的話就不能繼續(xù)落棋
    // 當(dāng)前格子有棋子的話也不能落棋
    if (
      calcWinner(currentBoard.squares) ||
      currentBoard.squares[squareIdx].squareContent !== null
    )
      return;

    const squares = currentBoard.squares.slice();
    squares[squareIdx].squareContent = currentBoard.nextPlayer;
    this.setState({
      history: history.concat([
        {
          squares,
          statusMsg: currentBoard.statusMsg,
          nextPlayer: this.togglePlayer(),
        },
      ]),
      historyIdx: history.length,
    });
  }

  jumpTo(historyIdx: number) {
    this.setState({
      historyIdx,
    });
  }

  render(): React.ReactNode {
    const history = this.state.history;
    const currentBoard = history[this.state.historyIdx];
    const { nextPlayer } = currentBoard;
    const winner = calcWinner(currentBoard.squares);
    let boardStatusMsg: string;

    if (winner !== null) {
      boardStatusMsg = `Winner is ${winner}!`;
    } else {
      boardStatusMsg = `Next player: ${nextPlayer}`;
    }

    const historyItems = history.map((_, idx) => {
      const desc = idx ? `Go to #${idx}` : `Go to game start`;
      return (
        <li key={idx}>
          <button className="history-item" onClick={() => this.jumpTo(idx)}>
            {desc}
          </button>
        </li>
      );
    });

    return (
      <div className="game">
        <div className="game-board">
          <Board
            squares={currentBoard.squares}
            statusMsg={boardStatusMsg}
            nextPlayer={nextPlayer}
            fillSquare={(squareIdx: number) => this.fillSquare(squareIdx)}
          />
        </div>
        <div className="divider"></div>
        <div className="game-info">
          <h1>History</h1>
          <ol>{historyItems}</ol>
        </div>
      </div>
    );
  }
}

const calcWinner = (squares: Squares): Player | null => {
  // 贏的時(shí)候的棋局情況
  const winnerCase = [
    [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 < winnerCase.length; i++) {
    const [a, b, c] = winnerCase[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a].squareContent as Player;
    }
  }

  return null;
};

 到此這篇關(guān)于React中井字棋游戲的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)React 井字棋游戲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React事件處理和表單的綁定詳解

    React事件處理和表單的綁定詳解

    這篇文章主要介紹了React事件處理和表單的綁定,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-08-08
  • 工程級(jí)?React?注冊(cè)登錄全棧級(jí)流程分析

    工程級(jí)?React?注冊(cè)登錄全棧級(jí)流程分析

    這篇文章主要介紹了工程級(jí)?React?注冊(cè)登錄全棧級(jí)流程,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-02-02
  • react實(shí)現(xiàn)pure render時(shí)bind(this)隱患需注意!

    react實(shí)現(xiàn)pure render時(shí)bind(this)隱患需注意!

    這篇文章主要為大家詳細(xì)介紹了值得你在react實(shí)現(xiàn)pure render的時(shí)候,需要注意的bind(this)隱患,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • 淺談React Component生命周期函數(shù)

    淺談React Component生命周期函數(shù)

    React組件有哪些生命周期函數(shù)?類組件才有的生命周期函數(shù),分為幾個(gè)階段:掛載,更新,卸載,錯(cuò)誤處理,本文主要介紹了這個(gè)階段,感興趣的可以了解一下
    2021-06-06
  • 如何在React?Native開(kāi)發(fā)中防止滑動(dòng)過(guò)程中的誤觸

    如何在React?Native開(kāi)發(fā)中防止滑動(dòng)過(guò)程中的誤觸

    在使用React?Native開(kāi)發(fā)的時(shí),當(dāng)我們快速滑動(dòng)應(yīng)用的時(shí)候,可能會(huì)出現(xiàn)誤觸,導(dǎo)致我們會(huì)點(diǎn)擊到頁(yè)面中的某一些點(diǎn)擊事件,誤觸導(dǎo)致頁(yè)面元素響應(yīng)從而進(jìn)行其他操作,表現(xiàn)出非常不好的用戶體驗(yàn)。
    2023-05-05
  • React中FormData的使用實(shí)例詳解

    React中FormData的使用實(shí)例詳解

    FormData 是一個(gè)非常靈活的工具,適用于處理表單數(shù)據(jù),特別是在需要發(fā)送文件或復(fù)雜表單數(shù)據(jù)時(shí),本文給大家介紹React中FormData的使用示例詳解,感興趣的朋友一起看看吧
    2025-04-04
  • react實(shí)現(xiàn)動(dòng)態(tài)增減表單項(xiàng)的示例代碼

    react實(shí)現(xiàn)動(dòng)態(tài)增減表單項(xiàng)的示例代碼

    在做項(xiàng)目的時(shí)候,甲方給的信息有限,網(wǎng)頁(yè)的備案信息寫成固定的,之后驗(yàn)收的時(shí)候,甲方要求把這個(gè)備案信息寫成動(dòng)態(tài)的,可以自增減,下面通過(guò)實(shí)例代碼給大家介紹react實(shí)現(xiàn)動(dòng)態(tài)增減表單項(xiàng)的示例,感興趣的朋友跟隨小編一起看看吧
    2024-05-05
  • React中Portal的具體使用

    React中Portal的具體使用

    React中的Portal允許開(kāi)發(fā)者將組件渲染到DOM樹的不同位置,本文主要介紹了React中Portal的具體使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-02-02
  • React-Hook中使用useEffect清除定時(shí)器的實(shí)現(xiàn)方法

    React-Hook中使用useEffect清除定時(shí)器的實(shí)現(xiàn)方法

    這篇文章主要介紹了React-Hook中useEffect詳解(使用useEffect清除定時(shí)器),主要介紹了useEffect的功能以及使用方法,還有如何使用他清除定時(shí)器,需要的朋友可以參考下
    2022-11-11
  • React為 Vue 引入容器組件和展示組件的教程詳解

    React為 Vue 引入容器組件和展示組件的教程詳解

    這篇文章主要介紹了React為 Vue 引入容器組件和展示組件的教程詳解,文中很詳細(xì)的給大家介紹了使用容器組件的原因,需要的朋友可以參考下
    2018-05-05

最新評(píng)論

沭阳县| 柘城县| 望城县| 长沙市| 柳州市| 天柱县| 噶尔县| 游戏| 武乡县| 邵武市| 雅江县| 庄浪县| 阳谷县| 崇左市| 隆化县| 华容县| 淮北市| 辛集市| 出国| 静乐县| 霍城县| 金平| 荔浦县| 长春市| 西畴县| 阿巴嘎旗| 石柱| 鹤山市| 东乡| 海门市| 曲阳县| 宜章县| 阿克陶县| 固原市| 铅山县| 威信县| 平泉县| 东港市| 缙云县| 清丰县| 湟源县|