React 02

1 React 组件的导出以及组件结构的组织

这段内容主要是关于 React 组件的导出以及组件结构的组织,我们逐步来解释:

1. export default 的作用

在 React 中,export default 是用来指定模块的默认导出的。一个模块(通常是一个文件)只能有一个默认导出。当其他文件引入这个模块时,默认导出的内容会被作为该模块的主要内容引入。

2. 组件导出的调整

  • 之前可能 Board 组件是用 export default 导出的,也就是:

    javascript 复制代码
    export default function Board() {
      //...
    }

    这时候,index.js(或者其他引入该组件的文件)会把 Board 作为顶层组件来使用。

  • 现在需要把 Game 组件作为顶层组件,所以要:

    • 移除 Board 组件前面的 export default

    • Game 组件添加 export default,也就是:

      javascript 复制代码
      export default function Game() {
        //...
      }

    这样,index.js 就会使用 Game 组件作为顶层组件,而不是 Board 组件了。

3. Game 组件的结构

javascript 复制代码
export default function Game() {
  return (
    <div className="game">
      <div className="game-board">
        <Board />
      </div>
      <div className="game-info">
        <ol>{/*TODO*/}</ol>
      </div>
    </div>
  );
}
  • 最外层的 div 类名为 game,作为整个游戏组件的容器。
  • 里面的 div 类名为 game-board,用来包裹 Board 组件,Board 组件负责渲染棋盘部分。
  • 另一个 div 类名为 game-info,里面的 <ol> 标签目前是一个待办(TODO),后续会用来展示游戏相关的信息,比如下棋的步骤、当前玩家、游戏结果等。

4. 总结

这段内容的核心是调整组件的默认导出,让 Game 成为顶层组件,同时 Game 组件的结构为后续添加游戏信息(如下棋历史、胜负判断等)预留了空间,使整个井字棋应用的结构更加清晰,便于后续功能的扩展。

2 状态管理与数组展开语法

这段代码和说明是关于 React(或类似前端框架中)状态管理与数组展开语法的应用,核心是理解如何通过数组展开语法来维护 "游戏步骤历史" 这类序列型状态。

1. 代码结构与核心逻辑

代码定义了一个 Game 组件(React 组件常见形式),内部有 handlePlay 函数,用于 ** 更新 "游戏历史" 和 "当前玩家(X/O 轮次)"** 这两个状态:

  • setHistory([...history, nextSquares]):更新 "游戏步骤历史" 的状态。
  • setXIsNext(!xIsNext):切换当前玩家(比如从 X 轮到 O 轮,或反之)。

2. 数组展开语法(...history)的作用

...数组展开语法 (也叫 "扩展运算符"),作用是:把原数组 history 里的所有元素 "拆出来",逐个放到新数组里

结合代码,[...history, nextSquares] 的效果是:

  • 先把 history 中已有的所有数组元素(每个元素代表某一步的游戏局面)依次放入新数组;
  • 再把 nextSquares(代表 "下一步的游戏局面")放到新数组的最后。

这样就得到了一个包含所有历史步骤 + 新步骤的 "完整历史数组"。

3. 示例理解(最关键的部分)

官方给的示例非常直观:

  • 假设 history 原本是 [[null,null,null], ["X",null,null]]
    • 第一个元素 [null,null,null]:代表 "游戏初始状态(所有格子都是空的)";
    • 第二个元素 ["X",null,null]:代表 "第一步,X 下在第一个格子,其余为空"。
  • 假设 nextSquares["X",null,"O"]:代表 "第二步,O 下在第三个格子,此时局面是 X 在第一格、O 在第三格,中间为空"。

那么 [...history, nextSquares] 生成的新数组就是:[[null,null,null], ["X",null,null], ["X",null,"O"]]------ 把 "初始状态""第一步""第二步" 这三个步骤,按顺序整合到了新的历史数组里。

4. 为什么要这么做?

在前端框架(如 React)中,状态是不可直接修改的(要通过 setState 这类方法更新)

如果直接对 history 数组 push 新元素,属于 "修改原数组",不符合框架的 "不可变状态" 设计思想。

而用 [...history, nextSquares],是创建了一个全新的数组 (包含历史 + 新步骤),再通过 setHistory 把 "新数组" 设为新状态 ------ 既维护了 "游戏步骤按顺序记录" 的逻辑,又符合 "状态不可变" 的要求。

总结:这段代码通过数组展开语法,优雅地实现了 "游戏步骤历史的追加式更新",保证每一步都被有序记录,同时遵循前端框架的状态管理规范。

3 React 中 key 属性的特点

key 是 React 中一个特殊的保留属性。创建元素时,React 提取 key 属性并将 key 直接存储在返回的元素上。尽管 key 看起来像是作为 props 传递的,但 React 会自动使用 key 来决定要更新哪些组件。组件无法询问其父组件指定的 key。

key 作为 React 内部用于优化渲染性能的特殊属性,有几个关键特性需要明确:

  1. 唯一性与作用域key 在兄弟节点之间必须唯一(无需全局唯一),React 通过它识别元素的身份,判断是复用还是重新创建组件。

  2. 非 props 特性 :尽管写法上类似 props(如 <Item key={id} />),但子组件无法通过 this.props.keyprops.key 获取 key 的值,它完全由 React 内部管理。

  3. 渲染优化核心 :当列表数据变化时,React 会对比新旧节点的 key

    • key 相同,尝试复用原有组件并更新内容;
    • key 不同,则销毁旧组件并创建新组件。
  4. 避免使用索引作为 key :在列表项可能重排、增删的场景中,使用索引作为 key 会导致 React 误判元素身份,反而影响性能或引发状态错乱,推荐使用数据自身的唯一标识(如 ID)。

理解 key 的作用机制,有助于更高效地编写 React 列表渲染逻辑,避免因 key 使用不当导致的性能问题或异常行为。


实战一:井子棋源代码

javascript 复制代码
import { useState } from 'react';

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

function Board({ xIsNext, squares, onPlay }) {
  function handleClick(i) {
    if (calculateWinner(squares) || squares[i]) {
      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>
    </>
  );
}

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 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;
}
相关推荐
Cxiaomu4 小时前
React Native 项目中 WebSocket 的完整实现方案
websocket·react native·react.js
浩男孩4 小时前
🍀我实现了个摸鱼聊天室🚀
前端
玲小珑4 小时前
LangChain.js 完全开发手册(十六)实战综合项目二:AI 驱动的代码助手
前端·langchain·ai编程
怀揣小梦想4 小时前
跟着Carl学算法--哈希表
数据结构·c++·笔记·算法·哈希算法·散列表
Nebula_g4 小时前
Java哈希表入门详解(Hash)
java·开发语言·学习·算法·哈希算法·初学者
励志不掉头发的内向程序员4 小时前
【STL库】哈希表的原理 | 哈希表模拟实现
开发语言·c++·学习·散列表
zyq~4 小时前
【课堂笔记】概率论-1
笔记·概率论
Small___ming4 小时前
【Linux基础学习】Linux Ubuntu 权限管理:从入门到精通
linux·学习·ubuntu
井柏然4 小时前
从 Monorepo 重温 ESM 的模块化机制
前端·javascript·前端工程化