发布于:2021-02-01 16:10:20
0
68
0
这是我第一次在博客上发表的关于学习React的文章的延续。我一直在努力学习reactjs.org,上一次,我在构建一个基本的tic-tac-toe游戏方面取得了进展。在这篇博文中,我将完成它!(希望如此!)
所以当我们上次结束时,我刚刚对用户选择正方形的能力进行了编码。但他们只能把方块变成“X”-es,没有任何机制让任何人获胜。显然,我们还有很多事情要做:
好吧,那么。。。什么?这篇课文有点混乱。我认为这是在说,我们不希望董事会必须不断地查询每个方块的状态,以确定是否有人赢得了比赛。这听起来像正方形将发送他们的状态时,他们更新(这应该只发生一次)和董事会将保持跟踪它从那一点。但是,就像我说的,我不确定,因为这段文字不是很清楚。
所以,这一节的标题是“提升状态”,这是我看到的下一段文字:
我不得不读几遍来解析它,但听起来好像是说,每当你想让两个组件互相对话时,它们必须通过父组件来实现。我不知道为什么。
……或者这篇文章(和上一篇文章)是说这样做是一种推荐的做法?是不是因为任何一个孩子都可以将自己的状态传递给父母,任何父母都可以设置孩子的状态,但孩子不能通过父母与其他孩子交谈?这就是为什么鼓励“提升状态”成为父母的原因吗?
这里的一些解释会非常有用。
我将此constructor
添加到Board
中,以将电路板的状态初始化为九个空方块:
constructor(props) { super(props); this.state = { squares: Array(9).fill(null) }; }
不过,在示例代码中,从squares: Array...
开始的行的末尾有一个悬空的逗号。我去掉了这个悬垂的逗号,我认为这是一个打字错误。
初始化this.state.squares
的语法与初始化单个方块中的this.state.value
的语法相似:
this.state = { value: null };
this.state = { squares: Array(9).fill(null) };
…除此之外,我们没有使用单个Square
中的单个value
,而是使用Array
的9
值,每个值默认设置为null
。我想是吧。
我甚至不知道发生了什么,但我现在看到了,是的。这里:
renderSquare(i) { return <Square value={i} />; }
…当我们渲染一个正方形时,我们向它发送值i
,该值由它在网格中的位置决定:
<div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div>
所以i = 1, 2, 3, ...
。但是Square
类中的实际render()
方法是:
render() { return ( <button className="square" onClick={() => this.setState({value: 'X'})}> {this.state.value} </button> ); }
它完全忽略传递给它的i
,它成为其state
的未使用部分:
constructor(props) { super(props); this.state = { value: null }; }
…并使用this.setState({value: 'X'})}
将值设置为X
,而不管传递给它的值是多少。假设下一步,我们将修复此行为,并允许将状态设置为X
或O
,具体取决于传递给renderSquare()
的值。
由于我们在Board.state.squares
中定义了电路板的状态(我们将在将来更新),因此我们可以通过改变renderSquare()
方法将一个正方形的状态(从该数组)传递给正方形:
renderSquare(i) { return <Square value={i} />; }
变成
renderSquare(i) { return <Square value={this.state.squares[i]} />; }
好的,既然游戏的状态被保存在Board
中,任何特定的Square
都不能直接更新游戏状态,因为对象不能直接编辑其他对象的状态。下一部分有点复杂。
首先,如果Square
不再跟踪游戏的状态,我们可以完全删除constructor
,因为它所做的一切都是设置Square
的状态:
class Square extends React.Component { constructor(props) { super(props); this.state = { value: null }; } render() { return ( <button className="square" onClick={() => this.setState({value: 'X'})}> {this.state.value} </button> ); } }
变成
class Square extends React.Component { render() { return ( <button className="square" onClick={() => this.setState({value: 'X'})}> {this.state.value} </button> ) ; } }
然后,我们将把一个函数从Board
传递到Square
,它告诉Square
如何处理点击,所以
renderSquare(i) { return <Square value={this.state.squares[i]} />; }
变成
renderSquare(i) { return ( <Square value = {this.state.squares[i]} onClick = {() => this.handleClick(i)} /> ); }
为便于阅读,行缩进,return
之后必须有一个()
围绕其内容。否则,JavaScript自动插入分号可能会破坏代码。(谁认为这是个好主意?)
当然,这意味着Square
也应该更新。我们应该在button
的定义中使用this.props.onClick()
而不是this.setState({value: 'X'})}
class Square extends React.Component { render() { return ( <button className="square" onClick={() => this.setState({value: 'X'})}> {this.state.value} </button> ); } }
变成
class Square extends React.Component { render() { return ( <button className="square" onClick={() => this.props.onClick()> {this.state.value} </button> ); } }
哦,当然,this.state.value
应该更改为this.props.value
,因为Square
的状态将从Board
发送到Square
的props
:
class Square extends React.Component { render() { return ( <button className="square" onClick={() => this.props.onClick()> {this.state.value} </button> ); } }
变成
class Square extends React.Component { render() { return ( <button className="square" onClick={() => this.props.onClick()> {this.props.value} </button> ); } }
我仍然不明白这一切是如何结合在一起的,但我猜这个解释正在进行中。
哦,是的,看,在那儿。我再次在终端中运行npm start
,等待代码运行的时间非常长。(还有其他人有这个问题吗?)当它出现时,我会在浏览器中看到一个错误页面:
我做了什么?
哦,看起来我忘了在我的代码中将{this.state.value}
更新为{this.props.value}
,尽管我是在这里写的。让我们改变一下,再试一次:
太好了,成功了!它应该以这种特定的方式崩溃,因为我们还没有在this.props
中定义onClick()
函数。
所以在我有this.props.onClick()
的地方,我应该换成this.props.handleClick()
。为了清楚起见,让我在这里复制整个index.js
文件:
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; class Square extends React.Component { render() { return ( <button className="square" onClick={() => this.props.handleClick()}> {this.props.value} </button> ); } } class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null) }; } renderSquare(i) { return ( <Square value={this.state.squares[i]} onClick={() => this.handleClick(i)} />; ); } render() { const status = 'Next player: X'; return ( <div> <div className="status">{status}</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 { render() { return ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{/* status */}</div> <ol>{/* TODO */}</ol> </div> </div> ); } } // ======================================== ReactDOM.render( <Game />, document.getElementById('root') );
我还漏掉了代码中的其他一些东西。在这里做笔记,并在终端中编辑代码,同时阅读教程可能会有点混乱。我认为以上所有内容都和教程中的一样,所以让我们继续。
为了消除第二个错误(“_this.props.onClick
不是函数”)并记住我们将onClick
重命名为handleClick
,我们现在必须在Board
中定义handleClick
方法:我不确定我真的从这个教程中学到了什么。不仅仅是复制和粘贴预先编写的代码。不过,我会坚持到底。
在Board
中,我们现在定义handleClick()
方法:
handleClick(i) { const squares = this.state.squares.slice(); squares[i] = 'X'; this.setState({squares: squares}); }
在我读之前,让我看看我是否能猜出这是在做什么。首先,它是一个接受单个参数的函数,这个参数是板上正方形的索引(无论是0-8
还是1-9
,我都不知道JavaScript是基于0
还是基于1
)。然后,它在方法中创建一个ant局部变量,并将其初始化为自己的state.squares
。如果squares
已经是一个数组,我不知道为什么slice()
需要出现在那里。另外,当我们在下一行中更改其一个元素的值时,为什么将squares
声明为const
?最后,我们用setState
设置状态。在JavaScript中,变量似乎是通过值传递的,因此我们必须显式地将squares.state
的值复制到一个局部变量中,然后编辑该变量,然后将编辑后的变量传递回以更改状态。有多少是对的?
……好吧,我想我以后会知道的。
这是字面上的下一段,开始解释这一点。如果你下次再谈的话,为什么还要说“我们稍后再解释”?这就是为什么他们建议这样做:
对我来说很自然的方法是直接编辑Square
的状态,但是教程推荐的方法是创建一个新对象,而不是改变现有对象。本教程建议尽可能保持对象不变,以便于检测更改,并且应用程序可以轻松恢复到以前的状态,以及其他好处。
天啊。可以。也许是我,因为我没有很强的JavaScript/反应式前端编程背景,但本教程似乎从一个概念跳到了另一个概念,基本上没有分段。似乎没有一个明确的目标或学习途径。我觉得我只是在学习一个随机的概念,然后复制一些代码,然后继续做下一件事。到目前为止还不怎么喜欢。
好吧,这看起来很简单。由于正方形本身不包含任何状态,因此它将通过在Board
中调用此函数来呈现。
好吧,但为什么。没有解释,我们继续下一件事。
在更改Board
中的renderSquare()
函数之前,我们将添加在电路板上绘制O
以及X
es的功能。我们在Board
“sconstructor
中设置初始状态:
class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null) }; } }
变成
class Board extends React.Component { constructor(props) { super(props); this.state = { squares: Array(9).fill(null), xIsNext: true }; } }
同样,在xIsNext: true
的末尾有一个悬空的逗号,我已经去掉了。这是故意的吗?
所以xIsNext
是一个布尔值,我们每次渲染正方形时都会翻转它。当我们重写renderSquare()
时(我想),我们将把xIsNext
从false翻转到true,反之亦然,在我们决定绘制X
或O
之前,检查xIsNext
的状态。我们改变
handleClick(i) { const squares = this.state.squares.slice(); squares[i] = 'X'; this.setState({squares: squares}); }
至
handleClick(i) { const squares = this.state.squares.slice(); squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ squares: squares, xIsNext: !this.state.xIsNext }); }
哦,打字错误。我来修一下。
快到了!正如你在上面看到的,游戏仍然没有宣布胜出。我想这是下一步要做的事情,但在我们做之前,教程希望我们呈现一个消息,说轮到谁了。我们添加一行:
const status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
…在Board
的render()
函数的顶部(现在,它总是说X
是下一个玩家):
我也注意到,当我编辑index.js
文件时,React会自动重新呈现localhost:3000
处的页面。那太好了!
好的,最后一件事最后一件事:我们如何宣布胜利者?
我现在真的没有精力了,所以我很高兴这一节快结束了。
我更喜欢一个教程,从最小的可理解的代码开始,从那里开始工作,而不是从一个骨架开始,一遍遍地说“好的,现在复制并粘贴这个内容到那里”。
在无意中复制了更多的代码之后。。。
本教程还有一节,但我严重缺乏完成它的动力。我想我想尝试一个不同的教程或书,从基础开始,并建立在他们的基础上。
我要退出这个教程75%的方式通过。我觉得很沮丧,而且我觉得我实际上没有学到多少反应。也许是在反应js.org我应该考虑为本教程做一些焦点小组测试,因为我确信我不是唯一一个有这种反应的人。
作者介绍