React基础
React 是一个由 Facebook(现 Meta)开发并维护的、开源的 JavaScript 库,主要用于 构建用户界面(UI),尤其是单页面应用程序中的动态、交互式界面。
简单示例:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // 使用 React Hooks 管理状态
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
一、创建React工程
创建 React 项目主要有三种主流方式,根据你的需求(如开发效率、性能要求、是否需要服务端渲染等),可灵活选择以下任一方法:
🚀 一、使用 Vite(推荐:速度快、现代化)
Vite 是当前最受欢迎的 React 项目初始化工具,启动速度和热更新远超传统工具,适合绝大多数新项目。
操作步骤:
确保已安装 Node.js(建议 v16+)。
在终端执行以下命令:
npm create vite@latest my-vite-app -- --template react
进入项目目录并安装依赖:
cd my-vite-app npm install
启动开发服务器:
npm run dev
- 访问
http://localhost:5173
查看项目。
- 访问
在Chrome浏览器中添加组件
React 因其高效、灵活和强大的社区支持,成为现代前端开发的主流选择之一。
在Chrome浏览器中添加组件
⚙️ 二、使用 Next.js(适合服务端渲染/静态站点)
若项目需服务端渲染(SSR)、静态生成(SSG)或强 SEO 支持(如博客、电商),Next.js 是最佳选择。
操作步骤:
- 创建项目:
npx create-next-app@latest my-next-app
- 进入目录并启动:
cd my-next-app npm run dev
- 访问
http://localhost:3000
。
- 访问
🧩 三、使用 create-react-app(传统方式,已逐渐淘汰)
虽然官方仍在维护,但启动慢、配置隐藏,仅适合学习或兼容旧项目。
操作步骤:
- 全局安装(可选):
npm install -g create-react-app
- 创建项目:
npx create-react-app my-app
- 启动项目:
cd my-app npm start
- 访问
http://localhost:3000
。
- 访问
📊 三种方式对比与适用场景
工具 | 优势 | 适用场景 | 启动命令 |
---|---|---|---|
Vite | ⚡ 超快启动、轻量、配置灵活 | 新项目、开发效率优先 | npm run dev |
Next.js | 🌐 服务端渲染、SEO 友好、全栈能力 | 博客、电商、需 SEO 的应用 | npm run dev |
CRA | 🧠 零配置、官方维护(但慢) | 学习 React 基础 | npm start |
❓ 常见问题解答(FAQ)
- Q1:是否需要全局安装工具?
不需要!现代工具(如 Vite、Next.js)通过npx
直接运行最新版本,避免全局依赖冲突。 - Q2:项目名称能否用大写字母?
不能!所有工具均要求项目名全小写(如my-app
)。 - Q3:如何修改默认端口?
在package.json
的启动命令后添加--port=新端口号
,例如:npm run dev -- --port=4000
💎 总结建议
- 新手/一般项目 → 选 Vite,体验流畅开发;
- 内容型网站/SEO 关键 → 选 Next.js,直接支持服务端渲染;
- 学习 React 基础 → 可尝试
create-react-app
,但注意其性能局限。
提示:以上命令均需在终端(如 VS Code 终端、命令行)执行。遇到依赖问题可尝试
npm install --force
强制重装。
二、JSX
2.1 关于 JSX
JSX(JavaScript XML) 是一种 JavaScript 的语法扩展(解析工具进行特定解析),由 Facebook 团队为 React 框架设计。它的核心作用是将 HTML 结构直接嵌入 JavaScript 代码中,实现声明式的 UI 构建。
核心特点:
类 HTML 语法:
允许在 JavaScript 中编写类似 HTML 的标签(例如<div>Hello</div>
)。JavaScript 表达式嵌入:
用{ }
包裹动态内容(如变量、函数调用):const name = "Alice"; const element = <h1>Hello, {name}</h1>; // 动态渲染
编译为 JavaScript:
JSX 本身不能被浏览器直接执行,需要通过工具(如 Babel)编译为标准 JavaScript:// 编译前 (JSX) <button className="btn">Click</button> // 编译后 (JavaScript) React.createElement("button", { className: "btn" }, "Click");
与 React 深度集成:
主要用于构建 React 组件的 UI 结构,但也可用于其他库(如 Vue 的 JSX 支持)。
2.2 关于 Babel (https://babeljs.io/)
Babel 是一个 JavaScript 编译器,它的核心功能是将新版本的 JavaScript 代码(或 JSX 等扩展语法)转换为向后兼容的旧版本 JavaScript,确保代码能在所有浏览器中运行。
关键作用:
- 编译 JSX:
将 JSX 语法转换为浏览器可理解的React.createElement()
调用(如上例所示)。 - 转译现代 JavaScript:
将 ES6+ 代码(如箭头函数、async/await
、类)转换为 ES5 等旧标准。// 编译前 (ES6) const list = items.map(item => <li key={item.id}>{item.name}</li>); // 编译后 (ES5) var list = items.map(function(item) { return React.createElement("li", { key: item.id }, item.name); });
- 插件化架构:
通过插件支持实验性语法(如装饰器)、类型检查(TypeScript)、代码优化等。
2.3 JSX 与 Babel 的关系
- 依赖 Babel 处理:
JSX 必须通过 Babel(或类似工具)编译为纯 JavaScript 才能运行。 - 常用预设:
在 React 项目中,通常使用 Babel 预设插件@babel/preset-react
来编译 JSX。 - 工作流程示例:
2.4 如何使用?
- 安装 Babel:
npm install @babel/core @babel/preset-react
- 配置 Babel(创建
.babelrc
文件):{ "presets": ["@babel/preset-react"] }
- 与构建工具集成:
配合 Webpack/Rollup 等工具自动完成编译(例如使用babel-loader
)。
✅ 总结:
- JSX 是描述 UI 的语法糖,需编译为 JavaScript。
- Babel 是编译工具链的核心,处理 JSX 和现代 JavaScript 的兼容性问题。
官网 babeljs.io 提供了实时编译演示、插件文档和配置指南。
二(支线)、JSX 中使用 JavaScript 表达式
在 JSX 中,你可以使用大括号 {}
来嵌入 JavaScript 表达式。这允许你在 JSX 中动态地插入值、执行计算和渲染逻辑。
2.1 基本用法
const name = 'John Doe';
const element = <h1>Hello, {name}</h1>;
2.2 表达式类型
变量引用
const user = { firstName: 'Jane', lastName: 'Doe' }; const element = <p>{user.firstName} {user.lastName}</p>;
函数调用
function formatName(user) {
return `${user.firstName} ${user.lastName}`;
}
const element = <h2>{formatName(user)}</h2>;
- 算术运算
const total = 10;
const count = 3;
const element = <p>Remaining: {total - count}</p>;
三元表达式
const isLoggedIn = true; const element = ( <div> {isLoggedIn ? 'Welcome back!' : 'Please sign in'} </div> );
数组方法(列表渲染)
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number}>{number}</li>
);
2.3 JSX 中的列表渲染示例
列表渲染是 React 中常见的操作,通常使用 map()
方法来遍历数组并生成一组 JSX 元素。下面提供多个不同场景的列表渲染示例:
2.3.1 基础列表渲染
const fruits = ['Apple', 'Banana', 'Orange'];
function FruitList() {
return (
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
);
}
2.3.2 对象数组渲染
const users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 }
];
function UserTable() {
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.age}</td>
</tr>
))}
</tbody>
</table>
);
}
2.3.3 带条件的列表渲染
在 JSX 中,我们经常需要根据条件来决定是否渲染某些内容。JavaScript 的逻辑与 (&&
) 运算符和三元运算符 (?:
) 是两种常用的条件渲染方式。
1. 逻辑与 (&&
) 运算符
{condition && <Component />}
当 condition
为真时,渲染 <Component />
;否则不渲染任何内容。
示例
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn && <h1>Welcome back!</h1>}
</div>
);
}
// 使用
<Greeting isLoggedIn={true} /> // 显示 "Welcome back!"
<Greeting isLoggedIn={false} /> // 不显示任何内容
特点
- 简洁,适合简单的条件渲染
- 当条件为
false
时,React 会跳过渲染(不会渲染null
或false
) - 注意:如果
condition
是0
这样的 falsy 值,会渲染0
而不是跳过
2. 三元运算符 (?:
)
{condition ? <ComponentA /> : <ComponentB />}
当 condition
为真时,渲染 <ComponentA />
;否则渲染 <ComponentB />
。
示例
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<h1>Welcome back!</h1>
) : (
<h1>Please sign in.</h1>
)}
</div>
);
}
// 使用
<Greeting isLoggedIn={true} /> // 显示 "Welcome back!"
<Greeting isLoggedIn={false} /> // 显示 "Please sign in."
特点
- 适合需要两种不同渲染结果的场景
- 更明确地表达两种可能性
- 可以嵌套使用(但不推荐过于复杂的嵌套)
3. 在 JSX 外部使用 if
语句
提前决定要渲染的内容
function Greeting({ isLoggedIn }) {
let content;
if (isLoggedIn) {
content = <h1>Welcome back!</h1>;
} else {
content = <h1>Please sign in.</h1>;
}
return <div>{content}</div>;
}
function renderContent(isLoggedIn) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
}
return <h1>Please sign in.</h1>;
}
function Greeting({ isLoggedIn }) {
return <div>{renderContent(isLoggedIn)}</div>;
}
4. 立即执行函数来包含 if
逻辑
function Greeting({ isLoggedIn }) {
return (
<div>
{(() => {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
} else {
return <h1>Please sign in.</h1>;
}
})()}
</div>
);
}
5.使用 switch
语句
function StatusIndicator({ status }) {
let indicator;
switch (status) {
case 'loading':
indicator = <Spinner />;
break;
case 'success':
indicator = <Checkmark />;
break;
case 'error':
indicator = <ErrorIcon />;
break;
default:
indicator = null;
}
return <div>{indicator}</div>;
}
6.比较
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
外部 if |
复杂条件逻辑 | 清晰易读 | 需要额外变量 |
IIFE | 简单内联条件 | 保持内联 | 语法稍复杂 |
提取函数 | 可复用的条件渲染 | 可复用,易于测试 | 需要跳转查看逻辑 |
switch |
多个互斥条件 | 结构清晰 | 代码量较多 |
&& 运算符 |
单一条件,渲染或不渲染 | 简洁 | 只能处理单一条件 |
三元运算符 | 二选一渲染 | 表达式形式,内联 | 嵌套时难以阅读 |
最佳实践建议
处理 false 值的注意事项
// 当 count 为 0 时,&& 会渲染 0
{count && <Message count={count} />}
// 修复方法:明确布尔值转换
{count > 0 && <Message count={count} />}
{!!count && <Message count={count} />}
- 简单条件:使用
&&
更简洁 - 二选一渲染:使用三元运算符
- 避免复杂嵌套:如果条件太复杂,考虑提取为函数或组件
- 保持可读性:复杂的条件逻辑可以拆分成多个变量
选择哪种方式主要取决于具体场景和个人/团队的编码风格偏好,重要的是保持代码的一致性和可读性。
const products = [
{ id: 1, name: 'Laptop', inStock: true },
{ id: 2, name: 'Phone', inStock: false },
{ id: 3, name: 'Tablet', inStock: true }
];
function ProductList() {
return (
<ul>
{products.map(product => (
product.inStock && (
<li key={product.id}>
{product.name} - {product.inStock ? 'In Stock' : 'Out of Stock'}
</li>
)
))}
</ul>
);
}
2.3.4 嵌套列表渲染
const departments = [
{
id: 1,
name: 'Development',
employees: ['John', 'Jane', 'Mike']
},
{
id: 2,
name: 'Design',
employees: ['Sarah', 'Tom']
}
];
function DepartmentList() {
return (
<div>
{departments.map(dept => (
<div key={dept.id}>
<h3>{dept.name}</h3>
<ul>
{dept.employees.map((employee, index) => (
<li key={index}>{employee}</li>
))}
</ul>
</div>
))}
</div>
);
}
2.3.5 使用组件渲染列表项
function TodoItem({ todo }) {
return (
<li>
<input type="checkbox" checked={todo.completed} />
<span>{todo.text}</span>
</li>
);
}
function TodoList() {
const todos = [
{ id: 1, text: 'Learn React', completed: true },
{ id: 2, text: 'Build a project', completed: false },
{ id: 3, text: 'Deploy to production', completed: false }
];
return (
<ul>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
2.3.6 重要注意事项
key 属性:列表中的每个元素都应该有一个唯一的
key
属性,通常使用数据中的 ID,// 好 {items.map(item => <li key={item.id}>{item.name}</li>)} // 如果没有 ID,可以使用索引(但不推荐用于动态列表) {items.map((item, index) => <li key={index}>{item.name}</li>)}
避免在渲染时修改数据:不要在
map
内部修改原始数组性能优化:对于大型列表,考虑使用虚拟滚动技术(如
react-window
库)
2.4 注意事项
- JSX 中的表达式必须是返回值的表达式,不能使用语句(如
if
语句) - 对于条件渲染,可以使用三元运算符或逻辑与 (
&&
) 运算符 - 对象不能直接作为子元素渲染,需要转换为字符串或提取特定属性
// 错误示例
const user = { name: 'John' };
const element = <div>{user}</div>; // 错误
// 正确示例
const element = <div>{user.name}</div>; // 正确
const element = <div>{JSON.stringify(user)}</div>; // 正确
JSX 中的 JavaScript 表达式使得动态 UI 的创建变得简单而强大。
三、事件绑定+组件
3.1 React事件绑定
在 JSX 中绑定 React 事件与在 HTML 中绑定 DOM 事件类似,但有一些关键区别。以下是 React 事件绑定的主要特点和使用方法:
3.1.1 基本语法
<button onClick={handleClick}>
点击我
</button>
(1)与 HTML 事件的区别
- 命名采用驼峰式:
onclick
→onClick
,onchange
→onChange
- 传入函数而不是字符串:
onclick="handleClick()"
→onClick={handleClick}
- 默认行为阻止方式不同:不能通过返回
false
来阻止默认行为
(2)常见事件类型
onClick
- 点击事件onChange
- 表单元素变化onSubmit
- 表单提交onMouseEnter
- 鼠标进入onMouseLeave
- 鼠标离开onKeyDown
- 键盘按下onFocus
- 获取焦点onBlur
- 失去焦点
3.1.2【1】事件处理函数
function handleClick(event) {
// event 是合成事件(SyntheticEvent)
console.log('按钮被点击了', event);
}
function App() {
return <button onClick={handleClick}>点击</button>;
}
3.1.2 【2】 传递参数
(1)如果需要传递额外参数给事件处理函数
function handleClick(id, event) {
console.log('ID:', id, '事件:', event);
}
function App() {
return (
<button onClick={(e) => handleClick(123, e)}>
带参数点击
</button>
);
}
🔍 特点分析:
- 事件对象正确传递:
onClick={(e) => handleClick(123, e)}
中的e
是 React 事件对象,被显式传递给了handleClick
函数。 - 函数签名匹配:
handleClick(id, event)
的两个参数(id
和event
)都得到了正确的值。 - 用途场景:当需要访问事件对象(如阻止默认行为、获取事件目标等)时,这种方式是推荐的。
(2)箭头函数的
不能直接写函数调用,这事件绑定需要一个函数引用
function handleClick(event) {
console.log('事件:', event);
}
const App = () => {
return (
<button onClick={() => handleClick(123)}>
带参数点击
</button>
);
};
🔍 问题分析:
- 事件对象未传递:
onClick={() => handleClick(123)}
中的handleClick
仅接收123
作为参数,事件对象(event
)没有被传入。 - 函数签名不匹配:
handleClick(event)
期望的是一个event
参数,但实际传入的是123
,因此event
参数会是undefined
。 - 潜在错误:如果
handleClick
中依赖event
的值(如event.target.value
、event.preventDefault()
等),会导致运行时错误或行为异常。
3.1.3 类组件中的绑定
在类组件中,通常需要在构造函数中绑定 this
或使用箭头函数:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('this:', this);
}
// 或者使用箭头函数自动绑定 this
handleClick = () => {
console.log('this:', this);
}
render() {
return <button onClick={this.handleClick}>点击</button>;
}
}
3.1.4阻止默认行为和事件冒泡
function handleClick(event) {
event.preventDefault(); // 阻止默认行为
event.stopPropagation(); // 阻止事件冒泡
console.log('事件处理');
}
3.1.5 合成事件(SyntheticEvent)
React 的事件是合成事件,是对原生事件的跨浏览器包装,具有与原生事件相同的接口,包括:
event.preventDefault()
event.stopPropagation()
event.target
event.currentTarget
3.1.6 注意事项
- 事件处理函数中的
this
默认不是组件实例,需要手动绑定 - 避免在 render 方法中创建新函数,可能导致不必要的重新渲染
- React 17+ 中事件委托不再附加到 document,而是附加到 React 渲染的根 DOM 容器
正确的事件绑定方式可以帮助你编写更高效、更易维护的 React 组件。
3.2 React组件
3.2.1 基础使用
React 组件是构建用户界面的独立、可复用的代码单元,将 UI 拆分为独立模块。每个组件管理自身的状态和逻辑,通过组合形成复杂界面。
/*原生写法*/
function Button (){
return <button>Button</button>
}
/*或者使用箭头函数的写法*/
const Button = () =>{
return <button>Button</button>
}
使用上
/*自闭和*/
<Button/>
/*成对标签*/
<Button/><Button/>
3.2.2 基础样式控制
在 React 中,控制组件样式有多种基础方式,每种方式各有适用场景。以下是主要方法及代码示例:
1. 内联样式 (Inline Styles)
直接在 JSX 元素中使用 style
属性,传入样式对象(属性名需驼峰命名)
function Button() {
return (
<button
style={{
padding: "10px 20px",
backgroundColor: "blue",
color: "white",
borderRadius: "4px",
cursor: "pointer"
}}
>
点击我
</button>
);
}
特点:
- ✅ 简单直接,适合动态样式
- ❌ 无法使用伪类(:hover)和媒体查询
- ❌ 性能不如 CSS 类(大量使用可能影响渲染)
2. 外部 CSS 文件
传统 CSS 引入方式(最常用)
/* Button.css */
.my-button {
padding: 10px 20px;
background-color: blue;
color: white;
border-radius: 4px;
cursor: pointer;
}
.my-button:hover {
background-color: darkblue; /* 支持伪类 */
}
组件中引入:
import "./Button.css";
function Button() {
return <button className="my-button">点击我</button>;
}
特点:
- ✅ 支持所有 CSS 特性
- ✅ 浏览器缓存优化
- ❌ 全局作用域(需注意类名冲突)
3. CSS Modules(推荐)
自动局部作用域(文件命名:[name].module.css
)
/* Button.module.css */
.button {
padding: 10px 20px;
background: blue;
}
组件中使用:
import styles from "./Button.module.css";
function Button() {
return <button className={styles.button}>点击我</button>;
}
编译后类名会变成唯一值(如 Button_button__1H2k5
)
特点:
- ✅ 避免类名冲突
- ✅ 支持所有 CSS 特性
- ✅ 与普通 CSS 写法一致
4. CSS-in-JS 库 (如 styled-components)
通过 JavaScript 编写样式(需安装库)
npm install styled-components
import styled from "styled-components";
// 创建带样式的组件
const StyledButton = styled.button`
padding: 10px 20px;
background: ${props => props.primary ? "blue" : "gray"};
color: white;
border-radius: 4px;
&:hover {
background: darkblue; /* 支持伪类 */
}
`;
function App() {
return (
<>
<StyledButton>普通按钮</StyledButton>
<StyledButton primary>主要按钮</StyledButton>
</>
);
}
特点:
- ✅ 动态样式支持最佳
- ✅ 自动处理作用域
- ❌ 增加包体积
- ❌ 学习曲线较陡
5. 实用类库 (Tailwind CSS)
原子化 CSS 方案(需配置)
function Button() {
return (
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
点击我
</button>
);
}
特点:
- ✅ 快速原型开发
- ✅ 响应式设计便捷
- ❌ 需学习类名系统
- ❌ HTML 可能臃肿
选择建议:
场景 | 推荐方式 |
---|---|
简单原型/少量动态样式 | 内联样式 |
传统项目/团队熟悉 CSS | 外部 CSS + BEM |
组件化/避免类名冲突 | CSS Modules |
复杂动态样式/主题系统 | CSS-in-JS |
快速开发/统一设计系统 | Tailwind CSS |
最佳实践提示:
- 大型项目推荐 CSS Modules 或 CSS-in-JS
- 避免过度使用内联样式(性能考虑)
- 动态样式可结合:
className
条件拼接 或 CSS 变量- 使用 :where() 降低 CSS 特异性(如
:where(.card) .title
)
四、useState
useState
是 React 中用于声明组件状态的一个 Hook,它让函数组件能够拥有自己的内部状态。(向组件添加一个状态变量,一旦变化UI也随之变化**(数据驱动视图**))
通过 useState
,你可以在组件内部保存数据并在数据发生变化时重新渲染组件。
4.1 useState 基础结构
const [state, setState] = useState(initialValue);
- state:当前状态值(只读)
- setState:更新状态的函数
- initialValue:状态的初始值(可以是任何类型)
4.2 状态不可变性原则详解
4.2.1 为什么状态必须不可变?
- 变更检测:React 通过引用比较检测状态变化
- 性能优化:避免不必要的重新渲染
- 可预测性:状态变更路径清晰可追踪
- 时间旅行调试:支持状态历史记录和回滚
4.2.2 错误 vs 正确更新方式对比
必须创建状态的新副本进行替换,而不是直接修改现有状态。
状态类型 | 错误方式(直接修改) | 正确方式(创建新值) |
---|---|---|
对象 | user.age = 31; setUser(user); |
setUser({...user, age: 31}); |
数组 | items.push('new'); setItems(items); |
setItems([...items, 'new']); |
嵌套对象 | user.address.city = 'Paris'; |
setUser({...user, address: {...user.address, city: 'Paris'}}); |
4.3 待处理:状态管理实战模式
1. 表单状态处理
function UserForm() {
const [user, setUser] = useState({
name: '',
email: '',
preferences: { theme: 'light', notifications: true }
});
const handleChange = (field, value) => {
setUser(prev => ({
...prev,
[field]: value
}));
};
const toggleTheme = () => {
setUser(prev => ({
...prev,
preferences: {
...prev.preferences,
theme: prev.preferences.theme === 'light' ? 'dark' : 'light'
}
}));
};
return (
<form>
<input value={user.name} onChange={e => handleChange('name', e.target.value)} />
<input value={user.email} onChange={e => handleChange('email', e.target.value)} />
<button type="button" onClick={toggleTheme}>
切换主题(当前:{user.preferences.theme})
</button>
</form>
);
}
2. 列表状态管理
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '学习React', completed: false }
]);
// 添加新任务(创建新数组)
const addTodo = text => {
setTodos(prev => [
...prev,
{ id: Date.now(), text, completed: false }
]);
};
// 切换任务状态(创建新对象)
const toggleTodo = id => {
setTodos(prev =>
prev.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
};
// 删除任务(创建过滤后的新数组)
const deleteTodo = id => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
return (
<div>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
4.4 待处理:高级技巧与优化策略
1. 函数式更新
// 当新状态依赖旧状态时使用
setCount(prevCount => prevCount + 1);
// 批量更新示例
const incrementTwice = () => {
setCount(prev => prev + 1); // 使用前值计算
setCount(prev => prev + 1); // 确保两次更新都生效
};
2. 惰性初始化
// 复杂初始状态的计算只执行一次
const [data, setData] = useState(() => {
const expensiveValue = calculateExpensiveValue();
return expensiveValue;
});
3. 状态结构优化
4. 使用 Immer 简化不可变更新
import produce from 'immer';
const [user, setUser] = useState({
name: 'Alice',
profile: {
level: 5,
achievements: ['新手', '探索者']
}
});
// 使用 Immer 进行"可变式"更新(底层仍不可变)
const addAchievement = title => {
setUser(produce(draft => {
draft.profile.achievements.push(title);
}));
};
const updateLevel = () => {
setUser(produce(draft => {
draft.profile.level += 1;
}));
};