前言:学习自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())
快速入门
创建和嵌套组件
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 列表。
-
定义数据源:先有一个包含多条数据的数组(通常来自状态或 props)。
const fruits = [
{ name: '草莓', id: 1 },
{ name: '香蕉', id: 2 },
{ name: '芒果', id: 3 }
]; -
用
map()转换为 JSX 列表 :遍历数组,为每个元素返回对应的 JSX 标签(如<li>)。// 遍历 fruits 数组,生成
- 元素集合
const fruitItems = fruits.map(fruit => {
return (
- {/* 注意:每个元素必须有 key 属性 */}
{fruit.name}
-
在组件中渲染列表 :将转换后的 JSX 列表嵌入到父标签(如
<ul>或<ol>)中。function FruitList() {
return- {fruitItems}
- 列表
}
);
});
最终渲染结果相当于:
<ul>
<li>草莓</li>
<li>香蕉</li>
<li>芒果</li>
</ul>
二、关键:key 属性的作用与规则
每个列表项必须添加 key 属性,这是 React 识别列表元素的"身份标识",直接影响渲染性能。
作用:
- 当列表数据发生变化(如新增、删除、排序)时,React 通过
key快速识别哪些元素没变、哪些变了,从而只更新变化的部分(而非重新渲染整个列表),提升效率。
规则:
- 唯一性 :
key在兄弟节点中必须唯一(不需要全局唯一)。
例:fruit.id是数据库中唯一的 ID,适合作为key。 - 稳定性 :
key的值应固定不变(除非元素本身被删除)。
❌ 不建议用数组索引(index)作为key(如果列表会重新排序/删除元素,索引会变化,导致 React 误判元素身份)。
✅ 优先用数据中自带的唯一标识(如id、uuid等)。
三、简化写法:直接在 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。在调用 useState 的 Square 的开头添加一个新行。让它返回一个名为 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。
- 状态私有性 :组件的
state是其内部封装的数据,仅由组件自身管理。根据 React 设计原则,子组件不能直接访问或修改父组件的state,这是为了保证组件的独立性和数据流向的可预测性。 - 单向数据流 :数据只能从父组件通过
props传递给子组件(向下流动);子组件若需影响父组件的数据,必须通过父组件预先提供的 "接口"(通常是回调函数)反向通知,而非直接修改
从 Board 组件向下传递一个函数到 Square 组件,然后让 Square 在单击方块时调用该函数。
1. 子组件(Square)声明事件接收接口
function Square({ value, onSquareClick }) {
return (
<button className="square" onClick={onSquareClick}>
{value}
</button>
);
}
Square组件通过props接收onSquareClick,这是一个从父组件(Board)传递过来的回调函数。button的onClick事件绑定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 更新状态,触发重渲染
}
// ...
}
handleClick是Board内部的函数,负责更新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 时会发生什么:
- 单击左上角的方块运行
button从Square接收到的onClickprops 的函数。Square组件从Board通过onSquareClickprops 接收到该函数。Board组件直接在 JSX 中定义了该函数。它使用参数0调用handleClick。 handleClick使用参数(0)将squares数组的第一个元素从null更新为X。Board组件的squaresstate 已更新,因此Board及其所有子组件都将重新渲染。这会导致索引为0的Square组件的valueprops 从null更改为X。
在 React 中,通常使用 onSomething 命名代表事件的 props,使用 handleSomething 命名处理这些事件的函数。
交替落子
每次玩家落子时,xIsNext(一个布尔值)将被翻转以确定下一个玩家,游戏 state 将被保存。你将更新 Board 的 handleClick 函数以翻转 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) 来检查玩家是否获胜。你可以在检查用户是否单击了已经具有 X 或 O 的方块的同时执行此检查。在这两种情况下,我们都希望尽早返回:
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">
// ...
)
}