React 教程中的小Demo(TypeScript)

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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值