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

news/2024/6/14 22:36:31 标签: react.js, javascript, 前端, js, es6

目录

  • 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 代表什么,哪里来的?

js">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. 把它输出试试
js">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 换一下试试
js">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 是什么

js">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 状态提升

js">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 副本

js">const squares = this.state.squares.slice();

这里 slice 什么用?

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

2.6 简化组件

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

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

简化为

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

2.7 key

js">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.niftyadmin.cn/n/51861.html

相关文章

Spring 5(黑马)

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

MySQl学习(从入门到精通12)

MySQl学习&#xff08;从入门到精通12&#xff09;第 15 章_存储过程与函数1. 存储过程概述1. 1 理解1. 2 分类2. 创建存储过程2. 1 语法分析2. 2 代码举例3. 调用存储过程3. 1 调用格式3. 2 代码举例3. 3 如何调试4. 存储函数的使用4. 1 语法分析4. 2 调用存储函数4. 3 代码举…

线程的生命周期和状态中方法的详解

点个关注&#xff0c;必回关 一、线程的生命周期和状态图&#xff1a; 图一&#xff1a; 图二 二、线程状态 1&#xff1a;新建 2&#xff1a;就绪&#xff08;拿到执行权开始运行&#xff09; 3&#xff1a;运行 4&#xff1a;阻塞 5&#xff1a;死亡 三、线程生命周期…

一个优质软件测试工程师的简历应该有的样子(答应我一定要收藏起来)

个人简历 基本信息 姓 名&#xff1a;xxx 性 别&#xff1a; 女 年 龄&#xff1a;24 现住 地址&#xff1a; 深圳 测试 经验&#xff1a;3年 学 历&#xff1a;本科 联系 电话&#xff1a;18xxxxxxxx 邮 箱&#xff1a;xxxxl163.com 求职意向 应聘岗位&#xff1a;软件…

[Nginx]Ngnix基础

文章目录一、基础1.1 what can nginx do1.1.1 基本HTTP服务1.1.2 高级HTTP服务1.1.3 Nginx常用的功能模块1.1.4 Nginx的核心组成1.2 why nginx?1.3 安装(略&#xff09;1.4 nginx的目录结构1.5 nginx.conf的几个关键配置keepalive_timeoutkeepalive_requestsserver块和locatio…

万里数据库加入龙蜥社区,打造基于“龙蜥+GreatSQL”的开源技术底座

近日&#xff0c;北京万里开源软件有限公司&#xff08;以下简称“万里数据库”&#xff09;及 GreatSQL 开源社区签署了 CLA&#xff08;Contributor License Agreement&#xff0c;贡献者许可协议&#xff09;&#xff0c;正式加入龙蜥社区&#xff08;OpenAnolis&#xff09…

Biome-BGC生态系统模型与Python融合技术实践应用

Biome-BGC是利用站点描述数据、气象数据和植被生理生态参数&#xff0c;模拟日尺度碳、水和氮通量的有效模型&#xff0c;其研究的空间尺度可以从点尺度扩展到陆地生态系统。在Biome-BGC模型中&#xff0c;对于碳的生物量积累&#xff0c;采用光合酶促反应机理模型计算出每天的…

Oracle数据库故障处理-单块读hang存储异常导致hang死,数据库大量的db file seq read等待(p1 p2无反映)

1 故障描述 2023年1月27日下午接到业务反馈数据库存在大量的锁表阻塞信息&#xff0c;并且业务的页面以及数据库的一些查询均处于阻塞状态&#xff0c;简单的查询sql也需要查询很长时间且未返回结果,数据库hang状态。 问题现象2 1 数据库进程无法杀除。 2 操作系统进程使用…