【React】从 0 开始学 React —— 实现井字棋小游戏

1 React 简介

React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库

官网提供了两种学习思路

如果你喜欢边做边学,请从实践教程开始。
如果你喜欢一步步学习概念,请从 Hello World 开始。

以下根据边学边做的方式,提供一种学习路线

2 实现井字棋小游戏

首先,根据官方提供的入门教程使用 react 实现井字棋小游戏,对 react 的相关概念有初步的了解,从而更好地上手 react。

最终完成的代码👉资源

你在实现过程中是否也遇到问题,有没有似懂非懂的时候?下面列举出一些点:

2.1 初始化

新建一个 react 项目

npx create-react-app my-app

2.2 props

在给棋盘增加数字这步,this.props 代表什么,哪里来的?

class Square extends React.Component {
  render() {
    return <button className="square">{this.props.value}</button>;
  }
}

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;
  }

  render() {
	  ...
  }
}
  1. 把它输出试试
class Square extends React.Component {
  render() {
    return (
          <button
            className="square"
            onClick={() => { console.log(this.props, "--this.props"); }}
          >
            {this.props.value}
          </button>
        );
  }
}

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />;
  }

  render() {
    ...
  }
}

点击 3 控制台输出以下信息:

在这里插入图片描述

this.propsBoard<Square value={i} /> 传入的 value={i} 变成的对象形式 {value: i}

  1. 那把 value 换一下试试
class Square extends React.Component {
  render() {
    return (
          <button
            className="square"
            onClick={() => { console.log(this.props, "--this.props"); }}
          >
            {this.props.title}  // value 换成 title
          </button>
        );
  }
}

class Board extends React.Component {
  renderSquare(i) {
    return <Square title={i} />;   // value 换成 title
  }

  render() {
    ...
  }
}

点击 3 控制台输出:

在这里插入图片描述

因此 Square 中的 props<Square /> 标签中传入的参数对象

2.3 setState

把棋盘上的数字换成点击显示X,这里的 this.setState 是什么

class Square extends React.Component {
  // 构造函数,props 为接收的参数
  constructor(props) {
    super(props); // 定义子类的构造函数时,都需要调用 super 方法
    this.state = { value: null }; // 给 Square 类定义一个属性 state
  }

  render() {
    return (
      // this.setState 更新 state 的数据
      <button className="square" onClick={() => this.setState({ value: "x" })}>
        {this.state.value}
      </button>
    );
  }
}

class Board extends React.Component {
  renderSquare(i) {
    return <Square value={i} />; // Square 的 this.props = {value: i}
  }

  render() {
	  ...
  }
}

关于setState官方文档是这样解释的:

setState() 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。
这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式

也就是 setStatestate{ value: null } 更新为 { value: "x" }

2.4 状态提升

class Square extends React.Component {
  render() {
    return (
      <button className="square" onClick={() => this.props.onClick()}>
        {this.props.value}   // 用 props 中的 value
      </button>
    );
  }
}

class Board extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      squares: Array(9).fill(null),  // 初始化数组
    };
  }

  handleClick(i) {
    const squares = this.state.squares.slice();
    squares[i] = "X";
    this.setState({ squares: squares });
  }

  renderSquare(i) {
    return (
      <Square
        value={this.state.squares[i]}
        onClick={() => this.handleClick(i)}
      />
    );
  }

  render() {
	  ...
  }
}

当前 state 没有保存在单个的 Square 组件中,而是保存在了 Board 组件中。
每当 Board 的 state 发生变化的时候,这些 Square 组件都会重新渲染一次。
把所有 Square 的 state 保存在 Board 组件中可以让我们在将来判断出游戏的胜者。

因为 Square 组件不再持有 state,因此每次它们被点击的时候,Square 组件就会从 Board 组件中接收值,并且通知 Board 组件。
在 React 术语中,我们把目前的 Square 组件称做“受控组件”。在这种情况下,Board 组件完全控制了 Square 组件。

状态提升,主要是两点变化:

  1. state 从在 Square 保存 改为在 Board 保存
  2. Square 组件从 Board 组件中接收 state 值

在这里插入图片描述在这里插入图片描述

2.5 副本

const squares = this.state.squares.slice();

这里 slice 什么用?

slice() 方法创建了 squares 数组的一个副本,而不是直接在现有的数组上进行修改,这样可以便于回溯历史数据

2.6 简化组件

组件只包含一个 render 方法,并且不包含 state,使用函数组件会更简单

class Square extends React.Component {
  render() {
    return (
      <button className="square" onClick={() => this.props.onClick()}>
        {this.props.value}
      </button>
    );
  }
}

简化为

function Square(props) {
  return (
    <button className="square" onClick={props.onClick}>
      {props.value}
    </button>
  );
}

2.7 key

class Game extends React.Component {
  constructor(props) {
    ...
  }

  handleClick(i) {
    ...
  }

  jumpTo(step) {
    ...
  }

  render() {
    ...
	
    const moves = history.map((step, move) => {
      const desc = move ? "Go to move #" + move : "Go to game start";
      return (
        <li key={move}>
          <button onClick={() => this.jumpTo(move)}>{desc}</button>
        </li>
      );
    });

    ...

    return (
      ...
    );
  }
}

每当一个列表重新渲染时,React 会根据每一项列表元素的 key 来检索上一次渲染时与每个 key 所匹配的列表项。

  • 发现不存在的 key,那么就会创建出一个新的组件。
  • 发现少了一个 key,那么就会销毁之前对应的组件。
  • 发现一个变化的 key,那么这个 key 所在的组件会被销毁,然后使用新的 state 重新创建一份。

key 是 React 中一个特殊的保留属性(还有一个是 ref,拥有更高级的特性)。
当 React 元素被创建出来的时候,React 会提取出 key 属性,然后把 key 直接存储在返回的元素上。

虽然 key 看起来好像是 props 中的一个,但是你不能通过 this.props.key 来获取 key
React 会通过 key 来自动判断哪些组件需要更新。组件是不能访问到它的 key 的。

  • 每次只要构建动态列表的时候,都要指定一个合适的 key
    • 如果你没有找到一个合适的 key,那么你就需要考虑重新整理你的数据结构了,这样才能有合适的 key
    • 如果你没有指定任何 key,React 会发出警告,并且会把数组的索引当作默认的 key
      • 但是如果想要对列表进行重新排序、新增、删除操作时,把数组索引作为 key 是有问题的。
      • 显式地使用 key={i} 来指定 key 确实会消除警告,但是仍然和数组索引存在同样的问题,所以大多数情况下最好不要这么做。

2.8 小结

将实现井字棋过程中的知识点简洁地列出:

  • 新建一个 react 项目
npx create-react-app 项目名
  • props:组件标签 中传入的参数对象
  • setState():更新 state 的数据
  • 状态提升:state 从在 子组件 保存改为在 父组件 保存,子组件父组件 中接收 state
  • 副本:用 slice() 方法创建数组的一个副本,而不是直接在现有的数组上进行修改,便于回溯历史数据
  • 简化组件:组件只包含一个 render 方法,并且不包含 state,使用函数组件会更简单
  • key:每当一个列表重新渲染时,React 会根据每一项列表元素的 key 来检索上一次渲染时与每个 key 所匹配的列表项
    • 一定要指定一个合适的 key ,最好不要用索引

还有一个命名规范,先记录以下,也许之后会用到:

  • 命名规范:React 命名规范,通常
    • 将代表事件的监听 prop 命名为 on[Event]
    • 将处理事件的监听方法命名为 handle[Event] 这样的格式

3 核心概念

React 官网

持续更新,未完待续…

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值