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

news2025/6/8 3:14:30

目录

  • 1 React 简介
  • 2 实现井字棋小游戏
    • 2.1 初始化
    • 2.2 props
    • 2.3 setState
    • 2.4 状态提升
    • 2.5 副本
    • 2.6 简化组件
    • 2.7 key
    • 2.8 小结
  • 3 核心概念

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 官网

持续更新,未完待续…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/334580.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3 组件之间的通信

组件之间的通信 经过前面几章的阅读&#xff0c;相信开发者已经可以搭建一个基础的 Vue 3 项目了&#xff01; 但实际业务开发过程中&#xff0c;还会遇到一些组件之间的通信问题&#xff0c;父子组件通信、兄弟组件通信、爷孙组件通信&#xff0c;还有一些全局通信的场景。 …

【GPLT 二阶题目集】L2-043 龙龙送外卖

参考地址&#xff1a;AcWing 4474. 龙龙送外卖&#xff08;杂题选讲&#xff09; 作者&#xff1a;yxc 感谢y总&#xff01; 龙龙是“饱了呀”外卖软件的注册骑手&#xff0c;负责送帕特小区的外卖。帕特小区的构造非常特别&#xff0c;都是双向道路且没有构成环 —— 你可以…

Spring Cloud Alibaba Sentinel 控制台

简介 Sentinel 控制台是流量控制、熔断降级规则统一配置和管理的入口&#xff0c;它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。在 Sentinel 控制台上&#xff0c;我们可以配置规则并实时查看流量控制效果。 下载 有以下两种方式获取Sentinel控制台 下…

进阶技术:Linux Arm32是如何调用C Main的

前言&#xff1a; Linux x64通过qemu的rdinit方式调用的C Main&#xff0c;实际上是通过load_elf_binary(加载和解析elf)和start_thread(设置Ip和sp)&#xff0c;用缺页异常来调用。关于这点可以看这篇文章&#xff1a;点击查看。那么Arm32里面是如何调用C Main的呢&#xff1f…

Android 进阶——Framework 核心之Binder 相关预备理论(一)

文章大纲引言一、进程的内存空间和进程隔离二、Linux 系统内存的用户空间和内核空间1、用户空间&#xff08;User Space&#xff09;2、内核空间&#xff08;Kernel Space&#xff09;三、Linux IPC 原理1、内核态和用户态2、IPC 步骤四、内核模块和驱动五、Binder1、Binder IP…

优思学院|從《狂飙》高启强爱看的《孙子兵法》到六西格玛项目管理

近期最受人瞩目的&#xff0c;无疑是电视剧《狂飙》中出类拔萃的反派高启强。而在剧中&#xff0c;指引高启强走向顶峰的&#xff0c;正是那部著名的军事经典——《孙子兵法》。 在剧中&#xff0c;高启强在一次村庄改造项目上遇到了困难&#xff0c;但他仍保持冷静&#xff0…

QT开发安卓程序初识

Qt | Qt For Android、Qt5.14.2安卓开发环境搭建详细步骤 测试结果如下&#xff1a;

AcWing 840. 模拟散列表

题目描述 餐前小菜&#xff1a; 在讨论本题目之前先看一个简单的问题&#xff1a;给出 NNN 个正整数 (a1,a2,...,an)(a_1,a_2,...,a_n)(a1​,a2​,...,an​)&#xff0c;再给出 MMM 个正整数 (x1,x2,...,xm)(x_1,x_2,...,x_m)(x1​,x2​,...,xm​)&#xff0c;问这 MMM 个数中…

冷知识|鹤顶红还能用来修长城?

大家好&#xff0c;我是建模助手。 在上篇浅浅地蹭了波热点之后&#xff0c;我灵机一动&#xff0c;倒不如也搞一搞建筑方面的冷知识&#xff1f;冷热搭配&#xff0c;事半功倍... 问问大家&#xff0c;如果谈起古建筑&#xff0c;关键词都有什么&#xff1f;是庄严、震撼、壮…

DHCP实验及配置

DHCP实验配置基于接口拓扑图配置基于全局拓扑图配置基于接口 拓扑图 配置 [Huawei]dhcp enable//在全局下使能DHCP服务 [Huawei]interface GigabitEthernet0/0/0//进入接口 [Huawei-GigabitEthernet0/0/0] ip address 192.168.1.1 255.255.255.0 //配置接口地址 [Huawei-Giga…

什么是QoS?QoS是如何工作的?QoS的实验配置如何进行?

QoS&#xff08;Quality of Service&#xff09;是服务质量的简称。对于网络业务来说&#xff0c;服务质量包括哪些方面呢&#xff1f; 从传统意义上来讲&#xff0c;无非就是传输的带宽、传送的时延、数据的丢包率等&#xff0c;而提高服务质量无非也就是保证传输的带宽&…

人工智能:分享五个目前最火的ChatGPT开源项目

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️荣誉&#xff1a; CSDN博客专家、数据库优质创作者&#x1f3c6;&…

Nacos简介(一)

目录 一、概览 二、注册中心基本概念 1) 什么是注册中心&#xff1f; 2) 如果没有注册中心&#xff1f;会怎样 3) 注册中心主要有三种角色&#xff1a; 4) 服务注册中心的作用 5&#xff09;CAP 理论 6&#xff09;CP和AP的选择 三、什么是 Nacos&#xff1f; 四、Nac…

C++——继承那些事儿你真的知道吗?

目录1.继承的概念及定义1.1继承的概念1.2 继承定义1.2.1定义格式1.2.2继承关系和访问限定符1.2.3继承基类成员访问方式的变化2.父类和子类对象赋值转换3.继承中的作用域4.派生类的默认成员函数5.继承与友元6. 继承与静态成员7.复杂的菱形继承及菱形虚拟继承如何解决数据冗余和二…

Spring 5(黑马)

文章目录传统JavaWeb开发的困惑IoC、DI和Aop思想提出Spring框架的诞生Spring 框架概述Spring 框架历史Spring Framework技术栈图示BeanFactory 快速入门DI 入门案例ApplicationContext快速入门BeanFactory 和 ApplicationContext的关系BeanFactory 的继承体系ApplicationContex…

全志H616——安装SQlite库并使用常用的数据库操作指令

在官网下载安装包&#xff1a;SQLite下载页面、https://www.sqlite.org/2022/sqlite-autoconf-3400100.tar.gz&#xff08;安装包&#xff09;下载到MobaXterm_Personal中解压&#xff1a;tar xvf sqlite-autoconf-3400100.tar.gz设置下载路径&#xff1a;./configure --help:.…

滴滴一面:order by 调优10倍,思路是啥?

背景说明&#xff1a; Mysql调优&#xff0c;是大家日常常见的调优工作。 所以&#xff0c;Mysql调优是一个非常、非常核心的面试知识点。 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;其相关面试题是一个非常、非常高频的交流话题。 近段时间&#xff0c;有小伙伴面…

计算机视觉 吴恩达 week 10 卷积

文章目录一、边缘检测二、填充 padding1、valid convolution2、same convolution三、卷积步长 strided convolution四、三维卷积五、池化层 pooling六、 为什么要使用卷积神经网络一、边缘检测 可以通过卷积操作来进行 原图像 n✖n 卷积核 f✖f 则输出的图像为 n-f1 二、填充…

【软考】系统集成项目管理工程师(二十)项目风险管理

一、项目风险管理概述1. 风险概念2. 风险分类3. 风险成本二、项目风险管理子过程1. 规划风险管理2. 识别风险3. 实施定性风险分析4. 实施定量风险分析5. 规划风险应对6. 控制风险三、项目风险管理流程梳理一、项目风险管理概述 1. 风险概念 风险是一种不确定事件或条件,一旦…

java赫夫曼编码

1.基本介绍 赫夫曼编码也翻译为 哈夫曼编码(Huffman Coding)&#xff0c;又称霍夫曼编码&#xff0c;是一种编码方式, 属于一种程序算法赫夫曼编码是赫哈夫曼树在电讯通信中的经典的应用之一。赫夫曼编码广泛地用于数据文件压缩。其压缩率通常在 20%&#xff5e;90%之间赫夫曼…