import React from 'react';
import './App.css';
/**
* 定义Square的props类型,在Typescript中必须要定义参数的类型(数据结构),所以将其封装成接口
* 第一可以更好的解耦和复用,第二可以方便修改和管理,props是经常修改的
*/
export interface SquareProps {
value: string, // 棋盘上显示的棋子
onClick: any // 从其父组件Board中传来的点击方法
}
export interface BoardProps {
squares: string[], // 保存棋盘的字符串数组
onClick: any // 从其父组件Game中传来的点击方法
}
export interface GameState {
history: {squares: string[]}[], // 保存历史棋盘的对象数组,每一个元素中保存一份历史,一个步骤就是一份历史
stepNumber: number, // 棋盘中当前是第几步
xIsNext: boolean // 下一步该谁下
}
// 最基础的组件,只负责渲染方格,方格中的值由Board组件传递,并绑定点击事件函数
function Square(props: SquareProps){
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
// 次级组件,组织Square,将其从Game组件中获取的square和onClick方法传通过props递给Square组件
class Board extends React.Component<BoardProps>{
renderSquare(i: number) {
return (
<Square value = {this.props.squares[i]}
onClick = {() => this.props.onClick(i)}
/>
);
}
render() {
return(
<div>
<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>
);
}
}
// 最高级组件,处理点击事件,更新棋盘,更新历史
class Game extends React.Component<any, GameState>{
// 因为Game是最高级的组件,它没有父组件,所以没有props传给它
constructor(props: any) {
// 在 JavaScript class 中,每次你定义其子类的构造函数时,都需要调用 super 方法。
// 因此,在所有含有构造函数的的 React 组件中,构造函数必须以 super(props) 开头。
super(props);
this.state = {
history: [{
//初始化history,其初始状态是一个长度为9且元素均为null的字符串数组
squares: Array(9).fill(null)
}],
// 初始化当前步骤数为0
stepNumber: 0,
// 初始化下一手为X
xIsNext: true
}
}
// 点击事件处理函数
handleClick(i: number) {
// slice(0, this.state.stepNumber + 1)将原历史拷贝一次,注意!history不包括当前的棋盘
const history = this.state.history.slice(0, this.state.stepNumber + 1);
// 上一轮棋盘存在于历史数组中的最后一位元素
const current = history[history.length - 1];
// 上一轮棋盘的squares的副本
const squares = current.squares.slice();
// 如果已经出现胜利者或者点击到的是已经被填充的方格直接返回,这一步比较巧妙
if (calculateWinner(squares) || squares[i]) {
return;
}
// 填充被点击方格,现在squares是现在棋盘的了
squares[i] = this.state.xIsNext ? 'X' : 'O';
// 更新Game的State,每次更新都会重新渲染组件
this.setState({
// 更新历史,将最近一轮的squares连接到history最后
history: history.concat([{
squares: squares
}]),
// 更新stepNumber,与history.length 同步
stepNumber: history.length,
// 下一手
xIsNext: !this.state.xIsNext
});
// this.setState 应该是异步的,在上面我们已经更新了history,但是在这里的输出仍是上一轮的history
// 后查阅React文档,证明这个想法是正确的,所以不要依赖setState的值来更新下一个值!
console.log(this.state.history);
}
jumpTo(step: number) {
// 更新Game的State,每次更新都会重新渲染组件
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0
});
}
render() {
let status;
const history = this.state.history;
const current = history[this.state.stepNumber];
const winner = calculateWinner(current.squares);
// 针对每轮棋盘做映射,这里的move是map中元素的index,相当于State中的stepNumber
const moves = history.map((step, move) => {
const desc = move ? 'Go to move # ' + move : 'Go to game start';
return (
// key是React中一个特殊的保留属性,用于甄别动态的列表元素,是同级元素中的唯一标记
<li key={move}>
<button onClick = {() => this.jumpTo(move)}>
{desc}
</button>
</li>
)
});
if (winner) {
status = 'Winner ' + winner;
} else {
status = 'Next Player ' + (this.state.xIsNext ? 'X' : 'O');
}
return(
<div className="game">
<div className="game-board">
<Board squares = {current.squares} onClick = {(i: number) => this.handleClick(i)}/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
function calculateWinner(squares: string[]) {
// 共8中胜利方式,三横三竖和两条对角线
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]) {
// 返回当前满足条件的方格中的值,X或O
return squares[a];
}
}
return null;
}
export default Game;
React 教程中的小Demo(TypeScript)
最新推荐文章于 2023-10-26 21:19:19 发布