在這裏閱讀效果更佳css
今天咱們就用代碼來重溫一下年少(假設你有react基礎,沒有也行,只要你會三大框架的任意一種,上手react不難)html
建議先全局安裝typescript 和 create-react-app(安裝過請忽略)react
npm install typescript create-react-app -g
使用typescript初始化項目web
create-react-app demo --typescript
初始化成功後ts環境已經配好了,不須要你手動加ts配置
此時就是tsx語法,咱們就能夠愉快的寫ts了
src文件夾就是開發目錄,全部代碼都寫在src文件夾下
咱們使用sass來寫樣式,先安裝sasstypescript
npm install node-sass --save
運行項目npm
npm run start
刪掉初始化界面的一些代碼數組
開發一個項目其實就是開發組件
把一個項目拆分一個個小組件,方便後期維護以及複用sass
react中組件分爲類組件和函數組件
須要管理狀態的最好使用類組件
因此咱們先把App改爲類組件app
import React from 'react'; import './App.css'; class App extends React.Component{ render(): React.ReactElement<any, string | React.JSXElementConstructor<any>> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined { return ( <div className="App"> </div> ); } }; export default App;
在src下新建component文件夾,在component文件夾下新建ChessComp.tsx,ChessComp.css
之後咱們的組件都放在component文件夾下
棋子組件咱們使用函數組件,思考須要傳入組件的屬性的類型:
棋子類型有三種(紅子 ,黑子, 空),
爲了約束棋子類型,咱們使用一個枚舉類型,
在src下新建types文件夾,專門放類型約束,
在types下新建enums.ts約束棋子類型
export enum ChessType { none, red, black }
並在棋子tsx中導入
傳入tsx的全部屬性用一個IProps接口約束
interface IProps { type: ChessType onClick?: () => void }
所有tsx代碼:
import React from 'react'; import {ChessType} from "../types/enums"; import './ChessComp.css'; interface IProps { type: ChessType onClick?: () => void } function ChessComp ({type, onClick}: IProps) { let chess = null; switch (type) { case ChessType.red: chess = <div className="red chess-item"></div>; break; case ChessType.black: chess = <div className="black chess-item"></div>; break; default: chess = null; } return ( <div className="chess" onClick={() => { if (type === ChessType.none && onClick) { onClick(); } }}> {chess} </div> ) }; export default ChessComp;
其中棋子只有爲none類型時才能被點擊
scss 代碼:
棋子咱們用背景顏色徑向漸變來模擬
$borderColor: #dddddd; $redChess: #ff4400; $blackChess: #282c34; .chess{ display: flex; justify-content: center; align-items: center; width: 50px; height: 50px; border: 2px solid $borderColor; box-sizing: border-box; cursor: pointer; .chess-item{ width: 30px; height: 30px; border-radius: 50%; } .red{ background: radial-gradient(#fff, $redChess); } .black{ background: radial-gradient(#fff, $blackChess); } }
同理在component文件夾下新建BoardComp.tsx,BoardComp.scss
棋盤組件咱們須要傳遞三個參數:
循環數組渲染棋子, 並給遊戲是否結束一個默認值
所有tsx代碼:
import React from 'react'; import {ChessType} from "../types/enums"; import ChessComp from "./ChessComp"; import "./BoardComp.scss"; interface IProps { chesses: ChessType[]; isGameOver?: boolean onClick?: (index: number) => void } const BoardComp: React.FC<IProps> = function(props) { // 類型斷言 const isGameOver = props.isGameOver as boolean; // 非空斷言 // const isGameOver = props.isGameOver!; const list = props.chesses.map((type, index) => { return ( <ChessComp type={type} key={index} onClick={() => { if (props.onClick && !isGameOver) { props.onClick(index) } }}/> ) }); return ( <div className="board"> {list} </div> ) }; BoardComp.defaultProps = { isGameOver: false }; export default BoardComp;
scss 代碼:
使用flex佈局
.board{ display: flex; flex-wrap: wrap; width: 150px; height: 150px; }
在component文件夾下新建Game.tsx,Game.scss
遊戲規則組件不須要傳參,咱們使用類組件來管理狀態
在types文件夾下的enums.ts裏新增遊戲狀態的枚舉類型
export enum ChessType { none, red, black } export enum GameStatus { /** * 遊戲中 */ gaming, /** * 紅方勝利 */ redWin, /** * 黑方勝利 */ blackWin, /** * 平局 */ equal, }
核心的代碼就是如何判斷遊戲的狀態,個人方法有點死,大家能夠本身重構,
import React from 'react'; import {ChessType, GameStatus} from "../types/enums"; import BoardComp from "./BoardComp"; import GameStatusComp from "./GameStatusComp"; import './Game.scss'; /** * 棋子的數組 * 遊戲狀態 * 下一次下棋的類型 */ interface Istate { chesses: ChessType[], gameStatus: GameStatus, nextChess: ChessType.red | ChessType.black } class Game extends React.Component<{}, Istate> { state: Istate = { chesses: [], gameStatus: GameStatus.gaming, nextChess: ChessType.black }; /** * 組件掛載完初始化 */ componentDidMount(): void { this.init(); } /** * 初始化9宮格 */ init() { const arr: ChessType[] = []; for (let i = 0; i < 9; i ++) { arr.push(ChessType.none) } this.setState({ chesses: arr, gameStatus: GameStatus.gaming, nextChess: ChessType.black }) } /** * 處理點擊事件,改變棋子狀態和遊戲狀態 */ handleChessClick(index: number) { const chesses: ChessType[] = [...this.state.chesses]; chesses[index] = this.state.nextChess; this.setState(preState => ({ chesses, nextChess: preState.nextChess === ChessType.black? ChessType.red : ChessType.black, gameStatus: this.getStatus(chesses, index) })) } /** * 獲取遊戲狀態 */ getStatus(chesses: ChessType[], index: number): GameStatus { // 判斷是否有一方勝利 const horMin = Math.floor(index/3) * 3; const verMin = index % 3; // 橫向, 縱向, 斜向勝利 if ((chesses[horMin] === chesses[horMin + 1] && chesses[horMin + 1] === chesses[horMin + 2]) || (chesses[verMin] === chesses[verMin + 3] && chesses[verMin + 3] === chesses[verMin + 6]) || (chesses[0] === chesses[4] && chesses[4] === chesses[8] && chesses[0] !== ChessType.none) || ((chesses[2] === chesses[4] && chesses[4] === chesses[6] && chesses[2] !== ChessType.none))) { return chesses[index] === ChessType.black ? GameStatus.blackWin : GameStatus.redWin; } // 平局 if (!chesses.includes(ChessType.none)) { return GameStatus.equal; } // 遊戲中 return GameStatus.gaming; } render(): React.ReactNode { return <div className="game"> <h1>三子棋遊戲</h1> <GameStatusComp next={this.state.nextChess} status={this.state.gameStatus}/> <BoardComp chesses={this.state.chesses} isGameOver={this.state.gameStatus !== GameStatus.gaming} onClick={this.handleChessClick.bind(this)}/> <button onClick={() => { this.init()} }>從新開始</button> </div>; } } export default Game;
樣式
.game{ position: absolute; display: flex; flex-direction: column; align-items: center; justify-content: space-around; top: 100px; width: 250px; height: 400px; left: 50%; transform: translateX(-50%); }
這個組件用來顯示狀態,在component文件夾下新建GameStatus.tsx,GameStatus.scss
沒什麼好說的,直接上代碼
import React from 'react'; import {ChessType, GameStatus} from "../types/enums"; import './GameStatus.scss'; interface Iprops { status: GameStatus next: ChessType.red | ChessType.black } function GameStatusComp(props: Iprops) { let content: JSX.Element; if (props.status === GameStatus.gaming) { if (props.next === ChessType.red) { content = <div className="next red">紅方落子</div> } else { content = <div className="next black">黑方落子</div> } } else { if (props.status === GameStatus.redWin) { content = <div className="win red">紅方勝利</div> } else if (props.status === GameStatus.blackWin) { content = <div className="win black">黑方勝利</div> } else { content = <div className="win equal">平局</div> } } return ( <div className="status"> {content} </div> ) } export default GameStatusComp;
.status { width: 150px; .next,.win{ font-size: 18px; } .win{ border: 2px solid; border-radius: 5px; width: 100%; padding: 10px 0; } .equal{ background-color: antiquewhite; } .red{ color: #ff4400; } .black{ color: #282c34; } }
最後在app.tsx裏調用game組件
import React from 'react'; import './App.scss'; import Game from "./component/Game"; class App extends React.Component{ render(): React.ReactElement<any, string | React.JSXElementConstructor<any>> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined { return ( <div className="App"> <Game/> </div> ); } }; export default App;