前言:学习自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
接收到的onClick
props 的函数。Square
组件从Board
通过onSquareClick
props 接收到该函数。Board
组件直接在 JSX 中定义了该函数。它使用参数0
调用handleClick
。 handleClick
使用参数(0
)将squares
数组的第一个元素从null
更新为X
。Board
组件的squares
state 已更新,因此Board
及其所有子组件都将重新渲染。这会导致索引为0
的Square
组件的value
props 从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">
// ...
)
}