前言
最近学习微前端,正好接触到了React.js,索性就系统完整地学习一遍。和当初学习Vue.js相同,先将官网的教程看一遍,在结合项目完整地做一个系统出来,从而达到融会贯通地目的。
三连棋完整功能代码
大家看了React.js的官网教程的话,就会发现和其他教程不同,React.js第一节课就是教会我们如何写一个游戏——三连棋,目的是让我接触和感受React.js中的组件、状态等一些概念,同时不失趣味性。官网已经有了绝大部分的游戏代码,但是也留下了几个小任务让我们去自主完成,从而检验我们的学习情况。话不多说,上完整代码:
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
function Square(props) {
// 任务2:在历史记录列表中加粗显示当前选择的项目
return (
<button className={'square' + (props.isActive?' active':'') + (props.highlight?' highlight':'')} onClick={props.onClick}>
{props.value}
</button>
)
}
class Board extends React.Component {
renderSquare(i) {
return (
<Square
highlight={this.props.highlight[0] === i || this.props.highlight[1] === i || this.props.highlight[2] === i}
key={i.toString()}
isActive={this.props.isActive === i}
value={this.props.squares[i]}
onClick={()=>this.props.onClick(i)}
/>
)
}
// 任务3:使用两个循环来渲染出棋盘的格子,而不是在代码里写死(hardcode)
render() {
let boardRow = []
for(let i = 0;i < 3;i++){
let square = []
for(let j = 0;j < 3;j++){
square.push(this.renderSquare(i*3 + j))
}
boardRow.push(
<div key={i} className="board-row">{square}</div>
)
}
return (
<div>
{boardRow}
</div>
);
}
}
class Game extends React.Component {
constructor(props){
super(props)
this.state = {
history:[{
// 当前棋盘所有棋子状态
squares:Array(9).fill(null),
// 当前棋子的落点坐标
position:null,
}],
// 当前棋子步数
stepNumber:0,
// 下一步是否是X
xIsNext:true,
// 当前记录按钮按照升序acs排列,默认升序
acs:true
}
}
// 棋盘落子事件,只需要管数据,渲染的事情交给render()
handleClick(i) {
const history = this.state.history.slice(0,this.state.stepNumber + 1);
const current = history[history.length - 1];
const squares = current.squares.slice();
// 如果已经有获胜者或者棋子,则没有任何反应
if (calculateWinner(squares) || squares[i]) {
return;
}
squares[i] =this.state.xIsNext ? 'X':'O';
// 更新最新的棋盘落点集合、当前步数、下一步棋手
this.setState({
history: history.concat([{
squares: squares,
position:i
}]),
stepNumber:history.length,
xIsNext: !this.state.xIsNext,
});
}
// 修改当前步数、下一步棋手,渲染的事情交给render()
jumpTo(step){
this.setState({
stepNumber: step,
xIsNext: (step % 2) === 0,
})
}
changeSort(acs){
this.setState({
acs:!acs
})
}
// (1)根据当前步数渲染棋盘current.squares
// (3)下一步棋手(或者已经胜利的棋手)status
// (3)根据所有的历史记录渲染历史记录按钮和当时的落子位置
render() {
const history = this.state.history
// (1)
const current = history[this.state.stepNumber]
const winnerInfo = calculateWinner(current.squares)
let acs = this.state.acs
let sort = []
// 任务4:添加一个可以升序或降序显示历史记录的按钮
sort.push(
<button key={acs} onClick={()=>{this.changeSort(acs)}}>{acs?'升序':'降序'}</button>
)
// (3)
const moves = history.map((step,move)=>{
// 是否降序
if(!acs){
move = history.length - 1 - move
}
const desc = move ? 'Go to move #' + move : 'Go to game start'
let position = history[move].position
position = calculatePosition(position)
// 任务1:在游戏历史记录列表显示每一步棋的坐标,格式为 (列号, 行号)
const positionDesc = move ? `position: (${position[0]},${position[1]})` : ''
return (
<li key={move}>
<button onClick={()=>{this.jumpTo(move)}}>{desc}</button>
<span>{positionDesc}</span>
</li>
)
})
let isActive = current.position
// 任务5:每当有人获胜时,高亮显示连成一线的 3 颗棋子
let info = [null,null,null]
// (2)
let status
if(winnerInfo){
status = 'Winner: ' + winnerInfo.winner
info = winnerInfo.info
}
else{
// 任务6:当无人获胜时,显示一个平局的消息
if(this.state.stepNumber === 9){
status = 'Draw!'
}
else{
status = 'Next player: ' + (this.state.xIsNext?'X':'O');
}
}
return (
<div className="game">
<div className="game-board">
<Board
highlight={info}
isActive={isActive}
squares={current.squares}
onClick={(i) => this.handleClick(i)}
/>
</div>
<div className="game-info">
<div>{status}</div>
<div>{sort}</div>
<ol>{moves}</ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
// 计算当前落子坐标
function calculatePosition(index) {
const positions = [
[1,1],[1,2],[1,3],
[2,1],[2,2],[2,3],
[3,1],[3,2],[3,3],
]
return positions[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 {
winner: squares[a],
info: lines[i]
}
}
}
return null;
}
index.css:
body {
font: 14px 'Century Gothic', Futura, sans-serif;
margin: 20px;
}
ol,
ul {
padding-left: 30px;
}
.game-info button {
background: aqua;
padding: 10px;
border: 0;
margin: 10px;
}
.board-row:after {
clear: both;
content: '';
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.active {
border: 3px solid #000;
line-height: 27px;
}
.highlight {
background: aqua;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
建议大家在学习后自己完成一遍这几个任务,同时也可以参考对照我的文章,相互学习进步。如果能够对我的代码提些宝贵的意见的话,那我必将不胜荣幸。