Marvel-Site Marvel-Site
首页
  • Java

    • Java基础
    • Java进阶
    • Java容器
    • Java并发编程
    • Java虚拟机
  • 计算机基础

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • Linux
  • 框架|中间件

    • Spring
    • MySQL
    • Redis
    • MQ
    • Zookeeper
    • Git
  • 架构

    • 分布式
    • 高并发
    • 高可用
    • 架构
  • 框架

    • React
    • 其他
  • 实用工具
  • 安装配置

    • Linux
    • Windows
    • Mac
  • 开发工具

    • IDEA
    • VsCode
  • 关于
  • 收藏
  • 草稿
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)

Marvel

吾必当乘此羽葆盖车
首页
  • Java

    • Java基础
    • Java进阶
    • Java容器
    • Java并发编程
    • Java虚拟机
  • 计算机基础

    • 数据结构与算法
    • 计算机网络
    • 操作系统
    • Linux
  • 框架|中间件

    • Spring
    • MySQL
    • Redis
    • MQ
    • Zookeeper
    • Git
  • 架构

    • 分布式
    • 高并发
    • 高可用
    • 架构
  • 框架

    • React
    • 其他
  • 实用工具
  • 安装配置

    • Linux
    • Windows
    • Mac
  • 开发工具

    • IDEA
    • VsCode
  • 关于
  • 收藏
  • 草稿
  • 索引

    • 分类
    • 标签
    • 归档
GitHub (opens new window)
  • 基础

  • 框架

    • React

      • React入门
      • React组件
      • DOM的Diffing算法
      • React脚手架介绍
      • React简单案例
      • React路由
      • redux
      • 井字棋
        • App.js
        • index.js
        • 构建棋盘
        • React开发者工具
        • 状态提升
        • 不变性
        • 交替落子
        • 宣布获胜者
        • 存储落子历史
    • 其他

  • Node

  • 前端
  • 框架
  • React
Marvel
2023-07-18
目录

井字棋

# 井字棋

  • 教程 (opens new window)
  • CodeSandboax (opens new window)

image.png

# App.js

创建组件,组件是一段可重用代码,它通常作为UI界面的一部分。组件用于渲染、管理和更新应用中的UI元素。

export default function Square() {
	return <button className="square">X</button>;
}
1
2
3

JavaScript关键字:export、default、return

  • export:使此函数可以在此文件之外访问;
  • default:表明该函数是文件中的主要函数;
  • return:后面的内容都作为值返回给函数的调用者;

<button>是JSX元素,JSX元素是JavaScript代码和HTML标签的组合,用于描述要显示的内容。className="square"是button的属性,他决定CSS如何设置按钮样式。

# index.js

它是App.js文件中创建的组件与Web浏览器之间的桥梁。

import React, { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./styles.css";

import App from "./App";

const root = createRoot(document.getElementById("root"));

root.render(
	<StrictMode>
		<App />
	</StrictMode>
);
1
2
3
4
5
6
7
8
9
10
11
12
13

其他文件将它们组合在一起,并将最终成果注入 public 文件夹里面的 index.html 中。

# 构建棋盘

React 组件必须返回单个 JSX 元素,不能像两个按钮那样返回多个相邻的 JSX 元素。要解决此问题,可以使用 <> 和 </> 来包裹多个相邻的 JSX 元素,如下所示:

export default function Square() {
	return (
		<>
			<button className="square">X</button>
			<button className="square">X</button>
		</>
	)
}
1
2
3
4
5
6
7
8

{value} 传参

function Square({value}) {
	function handleClick() {
		console.log('clicked!')
	}
	return <button className="square" onClick={handleClick}> {value} </button>
}

export default function Board() {
	return (
		<>
			<div className="board-row">		
				<Square value="1" />
				<Square value="2" />
				<Square value="3" />
			</div>
			...
		</>
	);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

React 提供了一个名为 useState 的特殊函数,可以从组件中调用它来让它“记住”一些东西。让我们将 Square 的当前值存储在 state 中,并在单击 Square 时更改它。

点击框框后,里面显示X

import { useState } from 'react'

function Square() {
  const [value, setValue] = useState(null);

  function handleClick() {
    setValue('X');
  }

  return (
    <button className="square" onClick={handleClick}> {value} </button>
  )
}

export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
      <div className="board-row">
        <Square />
        <Square />
        <Square />
      </div>
    </>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# React开发者工具

React 开发者工具可以检查 React 组件的 props 和 state。可以在 CodeSandbox 的 Browser 部分底部找到 React DevTools 选项卡: image.png

要检查屏幕上的特定组件,请使用 React 开发者工具左上角的按钮:

image.png

# 状态提升

state 对于定义它的组件是私有的,因此你不能直接从 Square 更新 Board 的 state。

要从多个子组件收集数据,或让两个子组件互相通信,要改为在起父组件中声明共享state。副组件通过props将该state传回给子组件。这使子组件彼此同步并与其父组件保持同步。

handleClick 函数使用 JavaScript 数组的 slice() 方法创建 squares 数组(nextSquares)的副本。

JavaScript 支持闭包,这意味着内部函数(例如 handleClick)可以访问外部函数(例如 Board)中定义的变量和函数。handleClick 函数可以读取 squares state 并调用 setSquares 方法,因为它们都是在 Board 函数内部定义的。

() => handleClick(0) 是一个箭头函数,它是定义函数的一种较短的方式。单击方块时,=>“箭头”之后的代码将运行,调用 handleClick(0)。

import { useState } from 'react'

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

export default function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));
  
  function handleClick(i) {
    const nextSquares = squares.slice();
    nextSquares[i] = "X";
    setSquares(nextSquares)
  }
  
  return (
    <>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

<Square value={squares[0]} onSquareClick={handleClick(0)} />

handleClick(0) 调用将成为渲染 Board 组件的一部分。因为 handleClick(0) 通过调用 setSquares 改变了棋盘组件的 state,所以你的整个棋盘组件将再次重新渲染。但这再次运行了 handleClick(0),导致无限循环:

Too many re-renders. React limits the number of renders to prevent an infinite loop.

# 不变性

function handleClick(i) {
	const nextSquares = squares.slice();
	nextSquares[i] = "X";
	setSquares(nextSquares)
}
1
2
3
4
5

调用了 .slice() 来创建 squares 数组的副本而不是修改现有数组。为了解释原因,我们需要讨论不变性以及为什么学习不变性很重要。

通常有两种更改数据的方法。第一种方法是通过直接更改数据的值来改变数据。第二种方法是使用具有所需变化的新副本替换数据。

【优点】:

  • 不变性使复杂的功能更容易实现。在本教程的后面,你将实现一个“时间旅行”功能,让你回顾游戏的历史并“跳回”到过去的动作。此功能并非特定于游戏——撤消和重做某些操作的能力是应用程序的常见要求。避免数据直接突变可以让你保持以前版本的数据完好无损,并在以后重用它们。
  • 默认情况下,当父组件的 state 发生变化时,所有子组件都会自动重新渲染。这甚至包括未受变化影响的子组件。尽管重新渲染本身不会引起用户注意(你不应该主动尝试避免它!),但出于性能原因,你可能希望跳过重新渲染显然不受其影响的树的一部分。不变性使得组件比较其数据是否已更改的成本非常低。

# 交替落子

import { useState } from 'react'
function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>{value}</button>
  );
}
export default function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));
  const [xIsNext, setXIsNext] = useState(true)
  
  function handleClick(i) {
    if (squares[i]) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }
    setXIsNext(!xIsNext)
    setSquares(nextSquares)
  }
  
  return (
    <>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 宣布获胜者

添加宣布获胜者

const winner = calculateWinner(squares);
let status;
if (winner) {
	status = "Winner: " + winner;
} else {
	status = "Next player: " + (xIsNext ? "X" : "O");
}
1
2
3
4
5
6
7

为啥这段代码直接放在里面了,不需要一个函数,每一次都执行渲染吗?

import { useState } from 'react'
function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>{value}</button>
  );
}
export default function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));
  const [xIsNext, setXIsNext] = useState(true)
  
  function handleClick(i) {
    if (squares[i] || calculateWinner(squares)) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }

    setXIsNext(!xIsNext)
    setSquares(nextSquares)
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = "Winner: " + winner;
  } else {
    status = "Next player: " + (xIsNext ? "X" : "O");
  }
  
  return (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6]
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

# 存储落子历史

需要更上级的组件 Game 存储state

当你在传递给 map 的函数中遍历 history 数组时,squares 参数遍历 history 的每个元素,move 参数遍历每个数组索引:0 、1、2……(在大多数情况下,你需要数组元素,但要渲染落子列表,你只需要索引。)

import { useState } from 'react'

export default function Game() {
  const [history, setHistory] = useState([Array(9).fill(null)]);
  const [currentMove, setCurrentMove] = useState(0);
  const xIsNext = currentMove % 2 === 0;
  const currentSquares = history[currentMove];


  function handlePlay(nextSquares) {
    const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
    setHistory(nextHistory);
    setCurrentMove(nextHistory.length - 1);
  }

  function jumpTo(nextMove) {
    setCurrentMove(nextMove);
  }

  const moves = history.map((squares, move) => {
    let description;
    if (move > 0) {
      description = 'Go to move #' + move;
    } else {
      description = 'Go to game start';
    }
    return (
      <li key={move}>
        <button onClick={() => jumpTo(move)}>{description}</button>
      </li>
    )
  })
  return (
    <div className="game">
      <div className="game-board">
        <Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay}/>  
      </div>
      <div className="game-info">
        <ol>{moves}</ol>
      </div>
    </div>
  );
}

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

function Board({xIsNext, squares, onPlay}) {
  function handleClick(i) {
    if (squares[i] || calculateWinner(squares)) {
      return;
    }
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }
    onPlay(nextSquares);
  }

  const winner = calculateWinner(squares);
  let status;
  if (winner) {
    status = "Winner: " + winner;
  } else {
    status = "Next player: " + (xIsNext ? "X" : "O");
  }
  
  return (
    <>
      <div className="status">{status}</div>
      <div className="board-row">
        <Square value={squares[0]} onSquareClick={() => handleClick(0)} />
        <Square value={squares[1]} onSquareClick={() => handleClick(1)} />
        <Square value={squares[2]} onSquareClick={() => handleClick(2)} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} onSquareClick={() => handleClick(3)} />
        <Square value={squares[4]} onSquareClick={() => handleClick(4)} />
        <Square value={squares[5]} onSquareClick={() => handleClick(5)} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} onSquareClick={() => handleClick(6)} />
        <Square value={squares[7]} onSquareClick={() => handleClick(7)} />
        <Square value={squares[8]} onSquareClick={() => handleClick(8)} />
      </div>
    </>
  );
}

function calculateWinner(squares) {
  const lines = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6]
  ];
  for (let i = 0; i < lines.length; i++) {
    const [a, b, c] = lines[i];
    if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
      return squares[a];
    }
  }
  return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

在VsCode里面编辑,<></>符号会报错,需要换成<div></div

编辑 (opens new window)
#前端#框架#React
上次更新: 2023/08/21, 20:05:58
redux
设计自己的谷歌插件 - QuickOpen

← redux 设计自己的谷歌插件 - QuickOpen→

最近更新
01
位运算
05-21
02
二叉树
05-12
03
Spring三级缓存解决循环依赖
03-25
更多文章>
Theme by Vdoing | Copyright © 2022-2024 Marvel
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式