绝对初学者学习React,第三部分

发布于:2021-02-01 16:10:20

0

68

0

javascript react 初学者 教程

这是我第一次在博客上发表的关于学习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,而是使用Array9值,每个值默认设置为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,而不管传递给它的值是多少。假设下一步,我们将修复此行为,并允许将状态设置为XO,具体取决于传递给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发送到Squareprops

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以及Xes的功能。我们在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,反之亦然,在我们决定绘制XO之前,检查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');

…在Boardrender()函数的顶部(现在,它总是说X是下一个玩家):

 

我也注意到,当我编辑index.js文件时,React会自动重新呈现localhost:3000处的页面。那太好了!

好的,最后一件事最后一件事:我们如何宣布胜利者?

我现在真的没有精力了,所以我很高兴这一节快结束了。

  

我更喜欢一个教程,从最小的可理解的代码开始,从那里开始工作,而不是从一个骨架开始,一遍遍地说“好的,现在复制并粘贴这个内容到那里”。

在无意中复制了更多的代码之后。。。

  

本教程还有一节,但我严重缺乏完成它的动力。我想我想尝试一个不同的教程或书,从基础开始,并建立在他们的基础上。

我要退出这个教程75%的方式通过。我觉得很沮丧,而且我觉得我实际上没有学到多少反应。也许是在反应js.org我应该考虑为本教程做一些焦点小组测试,因为我确信我不是唯一一个有这种反应的人。