快速学完React计划(第一天)

前言:学习自React官方中文文档,大部分为摘抄,其中有改动,加入自己的理解,供自己入门使用。已有typescipt、css、html、vue基础。看看自己多久能学完并且做项目!(只是为了监督懒惰的自己www)

目录

快速入门

创建和嵌套组件

[使用 JSX 编写标签](#使用 JSX 编写标签)

添加样式

展示变量

条件渲染

[1. if...else 语句(适合复杂逻辑 / 多分支)](#1. if...else 语句(适合复杂逻辑 / 多分支))

[2. 三元运算符(适合 JSX 内部的简单分支)](#2. 三元运算符(适合 JSX 内部的简单分支))

[3. 逻辑 && 运算符(适合 "仅当条件为真时显示")](#3. 逻辑 && 运算符(适合 “仅当条件为真时显示”))

[4. 条件性设置属性](#4. 条件性设置属性)

渲染列表

[一、基本用法:用 map() 渲染列表](#一、基本用法:用 map() 渲染列表)

[二、关键:key 属性的作用与规则](#二、关键:key 属性的作用与规则)

[三、简化写法:直接在 JSX 中嵌入 map()](#三、简化写法:直接在 JSX 中嵌入 map())

井字棋游戏教程

概览

App.js

index.js

构建棋盘

通过props传递数据

创建一个具有交互性的组件

React开发者工具

状态提升

交替落子

宣布获胜者


快速入门

创建和嵌套组件

React 应用程序是由 组件 组成的。一个组件是 UI(用户界面)的一部分,它拥有自己的逻辑和外观。组件可以小到一个按钮,也可以大到整个页面。

React 组件是返回标签的 JavaScript 函数

复制代码
function MyButton() {
  return (
    <button>我是一个按钮</button>
  );
}

至此,你已经声明了 MyButton,现在把它嵌套到另一个组件中:

复制代码
export default function MyApp() {
  return (
    <div>
    <h1>欢迎来到我的应用</h1>
    <MyButton />
    </div>
  );
}

React 组件必须以大写字母开头,而 HTML 标签则必须是小写字母。

使用 JSX 编写标签

上面所使用的标签语法被称为 JSX

JSX 比 HTML 更加严格。你必须闭合标签,如 <br />。你的组件也不能返回多个 JSX 标签。你必须将它们包裹到一个共享的父级中,比如 <div>...</div> 或使用空的 <>...</> 包裹:

复制代码
function AboutPage() {
  return (
    <>
      <h1>关于</h1>
      <p>你好。<br />最近怎么样?</p>
    </>
  );
}

添加样式

在 React 中,你可以使用 className 来指定一个 CSS 的 class。它与 HTML 的 class 属性的工作方式相同:

复制代码
<img className="avatar" />

然后,你可以在一个单独的 CSS 文件中为它编写 CSS 规则:

复制代码
.avatar {
  border-radius: 50%;

}

React 并没有规定你如何添加 CSS 文件。最简单的方式是使用 HTML 的 <link> 标签。

展示变量

1. 基础:在标签内容中展示变量

当需要把 JavaScript 变量的值显示在标签里时,用{}包裹变量,JSX 会自动解析并展示结果。

复制代码
// 定义数据
const user = {
  name: "李华",
  age: 25
};

// 组件中展示
function UserInfo() {
  return (
    <div>
      <p>姓名:{user.name}</p>  {/* 显示 user.name 的值:"李华" */}
      <p>年龄:{user.age}</p>   {/* 显示 user.age 的值:25 */}
    </div>
  );
}

2. 在属性中使用变量(替代字符串)

JSX 标签的属性默认用""包裹字符串,但如果属性值来自 JavaScript 变量,必须用{}包裹变量。

复制代码
const user = {
  avatarUrl: "https://example.com/avatar.jpg", // 图片地址变量
  nickname: "小花"
};

function UserAvatar() {
  return (
    // src属性值来自 user.avatarUrl 变量,用{}包裹
    <img 
      src={user.avatarUrl}  {/* 等价于 src="https://example.com/avatar.jpg" */}
      alt={"用户" + user.nickname + "的头像"}  {/* 字符串拼接后作为alt属性 */}
    />
  );
}

注意:

  • src="user.avatarUrl" 会被当作字符串 "user.avatarUrl",而不是变量
  • src={user.avatarUrl} 才会读取变量的值

3. 嵌入复杂表达式

{}中不仅能放变量,还能放任何有返回值的 JavaScript 表达式(计算、拼接、三元运算等)。

复制代码
const product = {
  name: "笔记本电脑",
  price: 4999,
  stock: 3
};

function ProductInfo() {
  return (
    <div>
      <h3>{product.name}</h3>
      <p>原价:{product.price}元</p>
      <p>折扣价:{product.price * 0.9}元</p>  {/* 计算:9折后的价格 */}
      <p>库存状态:{product.stock > 0 ? "有货" : "售罄"}</p>  {/* 三元运算判断状态 */}
    </div>
  );
}

4. 结合样式(style 属性)

当样式依赖 JavaScript 变量时,用style={``{}}语法:外层{}表示嵌入 JavaScript,内层{}是样式对象(键为驼峰式 CSS 属性)。

复制代码
const config = {
  fontSize: 16, // 字体大小变量
  color: "blue"
};

function StyledText() {
  return (
    <p 
      style={{ 
        fontSize: config.fontSize + "px",  // 用变量设置字体大小(拼接单位)
        color: config.color,               // 用变量设置颜色
        fontWeight: "bold"                 // 直接写固定样式值
      }}
    >
      这是一段带样式的文本
    </p>
  );
}

条件渲染

在 React 中,条件渲染完全依赖 普通 JavaScript 语法 实现,没有专门的特殊语法。以下是最常用的几种方式,按使用场景整理:

1. if...else 语句(适合复杂逻辑 / 多分支)

当条件判断较复杂,或需要处理多个分支时,适合先用 if...else 定义不同的 JSX 内容,再在渲染中引用。

复制代码
function UserPage({ isLoggedIn }) {
  // 用变量存储不同条件下的 JSX
  let pageContent;
  if (isLoggedIn) {
    // 已登录:显示用户面板
    pageContent = <UserDashboard />;
  } else {
    // 未登录:显示登录表单
    pageContent = <LoginForm />;
  }

  // 渲染存储的 JSX
  return <div>{pageContent}</div>;
}

优点:逻辑清晰,适合多条件(如 if...else if...else)或需要额外处理的场景。

2. 三元运算符(适合 JSX 内部的简单分支)

如果条件较简单,且希望直接在 JSX 中写判断,可以用 条件 ? 结果1 : 结果2 的三元语法。

复制代码
function Greeting({ isLoggedIn }) {
  return (
    <div>
      {/* 直接在 JSX 中通过三元判断显示内容 */}
      {isLoggedIn ? (
        <p>欢迎回来,用户!</p>  // 条件为 true 时显示
      ) : (
        <p>请先登录~</p>       // 条件为 false 时显示
      )}
    </div>
  );
}

适用场景:只有两个分支,且逻辑简单,希望代码更紧凑。

3. 逻辑 && 运算符(适合 "仅当条件为真时显示")

如果只需要在条件为 true 时显示内容,没有 else 分支,可用 条件 && 内容 简化代码。

复制代码
function Notification({ hasUnread }) {
  return (
    <div>
      <h1>消息中心</h1>
      {/* 只有 hasUnread 为 true 时,才显示未读提示 */}
      {hasUnread && <p>您有未读消息!</p>}
    </div>
  );
}

👉 原理:JavaScript 中 true && 表达式 会返回表达式,false && 表达式 会返回 false,而 React 会忽略 false 的渲染。

4. 条件性设置属性

以上逻辑也可用于动态设置标签的属性(如是否禁用按钮、是否添加类名等)。

复制代码
function SubmitButton({ isReady }) {
  return (
    <button
      // 条件为 true 时,添加 disabled 属性(值为 true);否则不添加
      disabled={!isReady}
      // 条件为 true 时,类名为 "active";否则为 "inactive"
      className={isReady ? "active" : "inactive"}
    >
      提交
    </button>
  );
}

渲染列表

在 React 中,渲染列表完全依赖 JavaScript 原生的数组方法(如 map())或循环语句,核心是将数组数据转换为 JSX 元素列表。以下是具体梳理:

一、基本用法:用 map() 渲染列表

map() 是数组的原生方法,能遍历数组并返回新的元素集合,非常适合在 React 中将数据数组转换为 JSX 列表。

  1. 定义数据源:先有一个包含多条数据的数组(通常来自状态或 props)。

    const fruits = [
    { name: '草莓', id: 1 },
    { name: '香蕉', id: 2 },
    { name: '芒果', id: 3 }
    ];

  2. map()转换为 JSX 列表 :遍历数组,为每个元素返回对应的 JSX 标签(如 <li>)。

    // 遍历 fruits 数组,生成

  3. 元素集合
    const fruitItems = fruits.map(fruit => {
    return (
  4. {/* 注意:每个元素必须有 key 属性 */}
    {fruit.name}

  5. );
    });

  6. 在组件中渲染列表 :将转换后的 JSX 列表嵌入到父标签(如 <ul><ol>)中。

    function FruitList() {
    return

      {fruitItems}
    ; // 渲染
  7. 列表
    }

最终渲染结果相当于:

复制代码
<ul>
  <li>草莓</li>
  <li>香蕉</li>
  <li>芒果</li>
</ul>

二、关键:key 属性的作用与规则

每个列表项必须添加 key 属性,这是 React 识别列表元素的"身份标识",直接影响渲染性能。

作用:

  • 当列表数据发生变化(如新增、删除、排序)时,React 通过 key 快速识别哪些元素没变、哪些变了,从而只更新变化的部分(而非重新渲染整个列表),提升效率。

规则:

  1. 唯一性key 在兄弟节点中必须唯一(不需要全局唯一)。
    例:fruit.id 是数据库中唯一的 ID,适合作为 key
  2. 稳定性key 的值应固定不变(除非元素本身被删除)。
    ❌ 不建议用数组索引(index)作为 key(如果列表会重新排序/删除元素,索引会变化,导致 React 误判元素身份)。
    ✅ 优先用数据中自带的唯一标识(如 iduuid 等)。

三、简化写法:直接在 JSX 中嵌入 map()

实际开发中,常将 map() 直接写在渲染的 JSX 中,减少中间变量:

复制代码
function FruitList() {
  const fruits = [
    { name: '草莓', id: 1 },
    { name: '香蕉', id: 2 },
    { name: '芒果', id: 3 }
  ];

  return (
    <ul>
      {/* 直接在 JSX 中用 map() 生成列表项 */}
      {fruits.map(fruit => (
        <li key={fruit.id}>
          {fruit.name}
        </li>
      ))}
    </ul>
  );
}

井字棋游戏教程

概览

App.js

第一行定义了一个名为 Square 的函数。JavaScript 的 export 关键字使此函数可以在此文件之外访问。default 关键字表明它是文件中的主要函数。

第二行返回一个按钮。JavaScript 的 return 关键字意味着后面的内容都作为值返回给函数的调用者。<button> 是一个 JSX 元素。JSX 元素是 JavaScript 代码和 HTML 标签的组合,用于描述要显示的内容。className="square" 是一个 button 属性,它决定 CSS 如何设置按钮的样式。X 是按钮内显示的文本,</button> 闭合 JSX 元素以表示不应将任何后续内容放置在按钮内。

index.js

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

复制代码
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

第 1-5 行将所有必要的部分组合在一起:

  • React
  • React 与 Web 浏览器对话的库(React DOM)
  • 组件的样式
  • App.js 里面创建的组件

构建棋盘

让我们回到 App.js。接下来我们将专注于这个文件。

目前棋盘只有一个方块,但你需要九个!如果你只是想着复制粘贴来制作两个像这样的方块:

复制代码
export default function Square() {
  return <button className="square">X</button><button className="square">X</button>;

}

错误的!

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

复制代码
export default function Square() {
  return (
    <>
    <button className="square">X</button>
    <button className="square">X</button>
    </>
  );
}

要把9个方块变成正方形,要添加CSS样式

复制代码
export default function Square() {
  return (
    <>
    <div className="board-row">
    <button className="square">1</button>
    <button className="square">2</button>
    <button className="square">3</button>
    </div>
    <div className="board-row">
    <button className="square">4</button>
    <button className="square">5</button>
    <button className="square">6</button>
    </div>
    <div className="board-row">
    <button className="square">7</button>
    <button className="square">8</button>
    <button className="square">9</button>
    </div>
    </>
  );
}

通过props传递数据

接下来,当用户单击方块时,我们要将方块的值从空更改为"X"。根据目前构建的棋盘,你需要复制并粘贴九次更新方块的代码(每个方块都需要一次)!但是,React 的组件架构可以创建可重用的组件,以避免混乱、重复的代码。

function Square({ value }) 表示可以向 Square 组件传递一个名为 value 的 props。

复制代码
function Square({ value }) {
  return <button className="square">{value}</button>;
}

然后,更新 Board 组件并使用 JSX 语法渲染 Square 组件:

复制代码
export default function Board() {
  return (
    <>
      <div className="board-row">
        <Square value="1" />
        <Square value="2" />
        <Square value="3" />
      </div>
      <div className="board-row">
        <Square value="4" />
        <Square value="5" />
        <Square value="6" />
      </div>
      <div className="board-row">
        <Square value="7" />
        <Square value="8" />
        <Square value="9" />
      </div>
    </>
  );
}

创建一个具有交互性的组件

我们希望 Square 组件能够"记住"它被单击过,并用"X"填充它。为了"记住"一些东西,组件使用 state

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

在文件的顶部导入 useState。从 Square 组件中移除 value props。在调用 useStateSquare 的开头添加一个新行。让它返回一个名为 value 的 state 变量:

复制代码
function Square() {
  const [value, setValue] = useState(null);

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

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

value 存储值,而 setValue 是可用于更改值的函数。传递给 useState 的 null 用作这个 state 变量的初始值,因此此处 value 的值开始时等于 null。

React开发者工具

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

状态提升

目前,每个 Square 组件都维护着游戏 state 的一部分。要检查井字棋游戏中的赢家,Board 需要以某种方式知道 9 个 Square 组件中每个组件的 state。

最好的方法是将游戏的 state 存储在 Board 父组件中,而不是每个 Square 中。Board 组件可以通过传递一个 props 来告诉每个 Square 显示什么,就像你将数字传递给每个 Square 时所做的那样。

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

重构 React 组件时,将状态提升到父组件中很常见。

让我们借此机会尝试一下。编辑 Board 组件,使其声明一个名为 squares 的 state 变量,该变量默认为对应于 9 个方块的 9 个空值数组:

复制代码
// ...
export default function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));
  return (
    // ...
  );
}

Array(9).fill(null) 创建了一个包含九个元素的数组,并将它们中的每一个都设置为 null。包裹它的 useState() 声明了一个初始设置为该数组的 squares state 变量。数组中的每个元素对应于一个 square 的值。当你稍后填写棋盘时,squares 数组将如下所示:

复制代码
['O', null, 'X', 'X', 'X', 'O', 'O', null, null]

现在你的 Board 组件需要将 value props 向下传递给它渲染的每个 Square

复制代码
export default function Board() {
  const [squares, setSquares] = useState(Array(9).fill(null));
  return (
    <>
      <div className="board-row">
        <Square value={squares[0]} />
        <Square value={squares[1]} />
        <Square value={squares[2]} />
      </div>
      <div className="board-row">
        <Square value={squares[3]} />
        <Square value={squares[4]} />
        <Square value={squares[5]} />
      </div>
      <div className="board-row">
        <Square value={squares[6]} />
        <Square value={squares[7]} />
        <Square value={squares[8]} />
      </div>
    </>
  );
}

接下来,你将编辑 Square 组件,以从 Board 组件接收 value props。这将需要删除 Square 组件自己的 value state 和按钮的 onClick props:

复制代码
function Square({value}) {
  return <button className="square">{value}</button>;
}

现在,每个 Square 都会收到一个 value props,对于空方块,该 props 将是 'X''O'null

  1. 状态私有性 :组件的 state 是其内部封装的数据,仅由组件自身管理。根据 React 设计原则,子组件不能直接访问或修改父组件的 state,这是为了保证组件的独立性和数据流向的可预测性。
  2. 单向数据流 :数据只能从父组件通过 props 传递给子组件(向下流动);子组件若需影响父组件的数据,必须通过父组件预先提供的 "接口"(通常是回调函数)反向通知,而非直接修改

Board 组件向下传递一个函数到 Square 组件,然后让 Square 在单击方块时调用该函数。

1. 子组件(Square)声明事件接收接口

复制代码
function Square({ value, onSquareClick }) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}
  • Square 组件通过 props 接收 onSquareClick,这是一个从父组件(Board)传递过来的回调函数。
  • buttononClick 事件绑定 onSquareClick,意味着:当用户点击该按钮时,会执行 onSquareClick 指向的函数。

2. 父组件(Board)定义状态更新逻辑

复制代码
export default function Board() {
  // 声明状态:squares 数组记录棋盘每个位置的状态('X'/'O'/null)
  const [squares, setSquares] = useState(Array(9).fill(null));

  // 定义状态更新函数:接收索引 i,更新对应位置为 'X'
  function handleClick(i) {
    // 关键:通过 slice() 创建 squares 的副本(保持数据不可变性)
    const nextSquares = squares.slice(); 
    nextSquares[i] = 'X'; // 修改副本中索引 i 的值
    setSquares(nextSquares); // 调用 setState 更新状态,触发重渲染
  }

  // ...
}
  • handleClickBoard 内部的函数,负责更新 squares 状态。它利用 React 的 setSquares 方法(由 useState 提供)触发状态更新,进而导致组件重渲染。
  • 这里使用 slice() 创建副本而非直接修改 squares(如 squares[i] = 'X'),是为了遵循 React 对 "不可变数据" 的推荐 ------ 避免直接突变状态,确保状态更新的可追踪性(便于后续时间旅行等功能)。

3. 父组件向子组件传递回调函数(带参数)

复制代码
// 在 Board 的 return 中,向每个 Square 传递 onSquareClick
<Square value={squares[0]} onSquareClick={() => handleClick(0)} />
<Square value={squares[1]} onSquareClick={() => handleClick(1)} />
// ... 其他 Square
  • 核心:传递给 onSquareClick 的是一个箭头函数() => handleClick(i)),而非直接调用 handleClick(i)
    • 若直接写 onSquareClick={handleClick(0)},则在 Board 渲染时会立即执行 handleClick(0),导致 setSquares 被调用,触发重渲染;重渲染时又会再次执行 handleClick(0),形成无限循环(控制台报错 Too many re-renders 的原因)。
    • 箭头函数 () => handleClick(i) 是一个 "待执行" 的函数引用:只有当 Square 中的按钮被点击时,才会执行 handleClick(i),避免了渲染阶段的提前调用。

为什么重新渲染会再吃执行handleClick函数?

当一个 React 组件(比如 Board)需要渲染(或重新渲染)时,会执行以下步骤:

  • 执行组件函数体(function Board() { ... } 内的所有代码)。
  • 生成 JSX 结构(即 return 中的内容)。
  • 将 JSX 转换为 DOM 操作,更新页面。

让我们回顾一下当用户单击你的棋盘左上角的方块以向其添加 X 时会发生什么:

  1. 单击左上角的方块运行 buttonSquare 接收到的 onClick props 的函数。Square 组件从 Board 通过 onSquareClick props 接收到该函数。Board 组件直接在 JSX 中定义了该函数。它使用参数 0 调用 handleClick
  2. handleClick 使用参数(0)将 squares 数组的第一个元素从 null 更新为 X
  3. Board 组件的 squares state 已更新,因此 Board 及其所有子组件都将重新渲染。这会导致索引为 0Square 组件的 value props 从 null 更改为 X

在 React 中,通常使用 onSomething 命名代表事件的 props,使用 handleSomething 命名处理这些事件的函数。

交替落子

每次玩家落子时,xIsNext(一个布尔值)将被翻转以确定下一个玩家,游戏 state 将被保存。你将更新 BoardhandleClick 函数以翻转 xIsNext 的值:

复制代码
export default function Board() {
  const [xIsNext, setXIsNext] = useState(true);
  const [squares, setSquares] = useState(Array(9).fill(null));

  function handleClick(i) {
    const nextSquares = squares.slice();
    if (xIsNext) {
      nextSquares[i] = "X";
    } else {
      nextSquares[i] = "O";
    }
    setSquares(nextSquares);
    setXIsNext(!xIsNext);
  }

  return (
    //...
  );
}

为了防止一个方格可以被重新覆盖:

复制代码
function handleClick(i) {
  if (squares[i]) {
    return;
  }
  const nextSquares = squares.slice();
  //...
}

宣布获胜者

很巧妙的判断方法

复制代码
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;
}

你将在 Board 组件的 handleClick 函数中调用 calculateWinner(squares) 来检查玩家是否获胜。你可以在检查用户是否单击了已经具有 XO 的方块的同时执行此检查。在这两种情况下,我们都希望尽早返回:

复制代码
function handleClick(i) {
  if (squares[i] || calculateWinner(squares)) {
    return;
  }
  const nextSquares = squares.slice();
  //...
}

为了让玩家知道游戏何时结束,你可以显示"获胜者:X"或"获胜者:O"等文字。为此,你需要将 status 部分添加到 Board 组件。如果游戏结束,将显示获胜者,如果游戏正在进行,你将显示下一轮将会是哪个玩家:

复制代码
export default function Board() {
  // ...
  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">
        // ...
  )
}
相关推荐
liangshanbo12153 小时前
React + TypeScript 企业级编码规范指南
ubuntu·react.js·typescript
苏打水com3 小时前
从 HTML/CSS/JS 到 React:前端进阶的平滑过渡指南
前端·javascript·html
一枚前端小能手3 小时前
🔐 单点登录还在手动跳转?这几个SSO实现技巧让你的用户体验飞起来
前端·javascript
小潘同学3 小时前
Vue3中响应式数据深度拷贝 Avoid app logic that relies on,,,,,,,,
前端
六六Leon3 小时前
Kuikly跨端模式接入资源管理
前端
tianchang3 小时前
深入理解 JavaScript 异步机制:从语言语义到事件循环的全景图
前端·javascript
旺仔牛仔QQ糖3 小时前
Vue3.0 Hook 使用好用多多
前端
~无忧花开~3 小时前
CSS学习笔记(五):CSS媒体查询入门指南
开发语言·前端·css·学习·媒体
程序猿小D3 小时前
【完整源码+数据集+部署教程】【零售和消费品&存货】价格标签检测系统源码&数据集全套:改进yolo11-RFAConv
前端·yolo·计算机视觉·目标跟踪·数据集·yolo11·价格标签检测系统源码