一、理解React、node.js和Vite的概念
(1)React
一个前端开发框架,它和vue是两个独立的、由不同团队开发的竞争性前端框架。React是meta公司的,vue是由尤雨溪开发的。
(2)node.js
前端开发(不论是vue还是react)都需要安装node.js,提供运行环境、npm包管理、构建工具。Node.js 之于 React,就像 JDK 之于 Spring Boot ------ 都是开发和构建时不可或缺的运行时环境,但 React 最终产品在浏览器运行(不像 Spring Boot 需要持续依赖 JVM)。
(3)vite
vite(也包括webpack)是 编译工具 + 打包工具 + 开发服务器 的组合,相当于 Java 的 Maven/Gradle + javac。

还有一个对比表:


二、vite+react项目
1.VS Code新建一个vite+react项目
步骤 1:打开 VS Code 终端
bash
# 方式1:快捷键
Ctrl + ` # 打开终端
# 方式2:菜单栏
Terminal → New Terminal

步骤 2:创建项目
bash
# npm 方式
npm create vite@latest my-react-app -- --template react

安装过程中可能会出现 ESLint安装失败的问题,有两种解决方法,一种是在项目中安装 ,
在项目根目录 (my-react-app)执行
bash
npm install eslint --save-dev
下载到 当前项目的 node_modules 目录,并在 package.json 中记录到 devDependencies。
另一种是全局安装:
bash
npm install -g eslint
安装完成后重启VSCode。
步骤 3:npm install 下载包
npm install 只下载 当前项目所需的包(根据 package.json 中的依赖)
步骤 4:手动启动服务
bash
npm run dev
补充:改变npm安装包的目录
使用npm install -g 全局安装指令Windows的默认安装目录为:
bash
C:\Users\你的用户名\AppData\Roaming\npm\node_modules
可以改变目录。
第一步:创建新文件夹
在你想要的位置(比如 D 盘)新建两个文件夹:
node_global:用来存放所有全局安装的包。
node_cache:用来存放 npm 的缓存文件。
例如,你可以在 D 盘根目录下创建这两个文件夹:
D:\software\nodejs\node_global 和 D:\software\nodejs\node_cache
第二步:修改 npm 配置
打开终端(命令提示符或 PowerShell),执行以下两条命令,告诉 npm 使用我们刚才新建的文件夹:
bash
npm config set prefix "D:\software\nodejs\node_global"
npm config set cache "D:\software\nodejs\node_cache"
执行完后,可以运行 npm config ls 检查一下
第三步:配置环境变量
这是最关键的一步,目的是让你在电脑任何地方都能运行全局命令。
打开"系统环境变量"设置窗口(在搜索框搜"环境变量"即可找到)。
修改用户变量:
在"用户变量"区域,找到并选中 Path 变量,然后点击"编辑"。
在弹出的窗口中,点击"新建",将 D:\software\nodejs\node_global(你的新路径)加进去。
完成上述步骤后,请重启你的终端(以管理员身份运行 ),然后随意全局安装一个包(比如 npm install -g prettier)来测试。
安装完成后,去你新设的 node_global 文件夹里看看,如果能找到刚才安装的包,就说明一切配置成功了。
2. vite+react目录结构
新建的vite-react的项目目录结构如下:

完整的目录结构:
bash
my-react-app/ # 项目根目录
├── node_modules/ # 依赖包目录(自动生成,不手动修改)
├── public/ # 公共静态资源目录
│ └── vite.svg # 网站图标(可替换)
├── src/ # 源代码目录(主要开发区域)
│ ├── assets/ # 资源文件目录
│ │ └── react.svg # React 图标
│ ├── App.jsx # 根组件
│ ├── App.css # 根组件样式
│ ├── main.jsx # 入口文件
│ └── index.css # 全局样式
├── .gitignore # Git 忽略文件配置
├── index.html # 主 HTML 文件
├── package.json # 项目配置文件,类似于springboot的pom.xml
├── package-lock.json # 依赖版本锁定文件
├── vite.config.js # Vite 配置文件
└── README.md # 项目说明文档
核心文件详解。
1.入口index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body>
<!-- React 会挂载到这个 div 上 -->
<div id="root"></div>
<!-- 入口 JS 文件 -->
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
2.JavaScript 入口src/main.jsx
javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
// 找到 root 元素,将 React 应用渲染进去
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
作用:
整个应用的起点
将 App 组件渲染到 HTML 中
引入全局样式
3.根组件src/App.jsx
javascript
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
</div>
</>
)
}
export default App
三、React 常用语法和渲染结构
3.1 JSX核心语法
3.1.1 基本语法规则
1.定义组件
组件包括类组件和函数组件,类组件逐渐被淘汰,React 官方现在推荐只使用函数组件。
类组件定义语句(不过多展开了):
javascript
import React, { Component } from 'react';
// 类组件
class Welcome extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<h1>Hello, {this.props.name}</h1>
<p>计数: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
增加
</button>
</div>
);
}
}
函数组件定义语句:
javascript
//.jsx
import React, { useState } from 'react';
function Counter() {
// 声明一个叫 "count" 的状态变量,初始值为 0
//创建一个修改函数 setCount,用于更新 count 的值
//返回一个数组,包含 [当前值, 修改函数]
const [count, setCount] = useState(0);
return (
<div>
<p>当前计数: {count}</p>
{/* 点击按钮时,调用 setCount 修改 count 的值 */}
<button onClick={() => setCount(count + 1)}>
增加
</button>
<button onClick={() => setCount(count - 1)}>
减少
</button>
<button onClick={() => setCount(0)}>
重置
</button>
</div>
);
}
说明:组件的return语句只能返回一个根元素
javascript
//.jsx
// ✅ 只能返回一个根元素
function Component() {
return (
<div> {/* 用一个 div 包裹 */}
<h1>标题</h1>
<p>内容</p>
</div>
)
}
javascript
//jsx
// ✅ 使用空标签(Fragment)
function Component() {
return (
<>
<h1>标题</h1>
<p>内容</p>
</>
)
}
javascript
//jsx
// ❌ 错误:相邻的 JSX 元素
function Component() {
return (
<h1>标题</h1>
<p>内容</p> // 报错!
)
}
组件可以导出给其他组件使用
javascript
//.jsx
// components.jsx
export default function UserCard({ name, age, email }) {
return (
<div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}>
<h3>{name}</h3>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
</div>
);
}
/* 或者
function UserCard({ name, age, email }) {
return (
<div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}>
<h3>{name}</h3>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
</div>
);
}
export default UserCard;*/
javascript
//App.jsx
//其他组件
import UserCard from './components';
// ❌ 错误:不能加花括号
// import { UserCard } from './components';
// ✅ 可以用 as 重命名
//import { UserCard as MyCard } from './components';
function App() {
const users = [
{ name: '张三', age: 25, email: 'zhangsan@example.com' },
{ name: '李四', age: 30, email: 'lisi@example.com' }
];
return (
<div>
{users.map(
(user, index) => (<UserCard key={index} {...user} />)//...是对象展开运算符,{...user} 将 user 对象的所有属性作为 props 传递,// 等价于<UserCard key={index} name={user.name} age={user.age} email={user.email} />
)
}
</div>
);
}
export default App;
一个.jsx文件中可以有多个函数组件(一般是一个主组件,其他辅组件),且同一文件中的组件可以互相使用。
javascript
//.jsx
// Components.jsx
import React from 'react';
// 子组件
function UserCard({ name, age, email }) {
return (
<div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}>
<h3>{name}</h3>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
</div>
);
}
function Title({ text }) {
return <h1 style={{ color: 'blue' }}>{text}</h1>;
}
// 主组件(默认导出)
function App() {
const users = [
{ name: '张三', age: 25, email: 'zhangsan@example.com' },
{ name: '李四', age: 30, email: 'lisi@example.com' }
];
return (
<div>
<Title text="用户列表" />
{users.map(
(user, index) => (<UserCard key={index} {...user} />)//...是对象展开运算符,{...user} 将 user 对象的所有属性作为 props 传递,// 等价于<UserCard key={index} name={user.name} age={user.age} email={user.email} />
)
}
</div>
);
}
export default App;
一个jsx文件也可以导出多个组件(及常量)供其他文件使用,主组件为默认导出组件,其他组件也可以一起导出。在其他文件中导入默认导出组件不用加花括号,其他组件需要加花括号。
javascript
// components.jsx
import React from 'react';
// 定义多个组件
export function Header() {
return <header>这是头部</header>;
}
export function Footer() {
return <footer>这是底部</footer>;
}
export function Sidebar() {
return <aside>侧边栏</aside>;
}
//或者
/*function Header() {
return <header>这是头部</header>;
}
function Footer() {
return <footer>这是底部</footer>;
}
function Sidebar() {
return <aside>侧边栏</aside>;
}
export { Header, Footer,Sidebar};*/
// 默认导出一个主组件
export default function Layout({ children }) {
return (
<div>
<Header />
{children}
<Footer />
</div>
);
}
// 也可以单独导出常量
export const API_URL = 'https://api.example.com';
export const VERSION = '1.0.0';
javascript
// App.jsx
import Layout, { Header, Footer, Sidebar , API_URL, VERSION } from './components';
function App() {
return (
<Layout>
<Sidebar />
<div>主要内容</div>
</Layout>
<h2>API地址: {API_URL}</h2>
<h3>版本: {VERSION}</h3>
);
}
2.使用组件
先来补充一个概念Props。Props 是 Properties 的缩写,是父组件传递给子组件的数据。它是 React 组件之间通信的核心机制。
对于定义好的组件:
javascript
import React from 'react';
export default function UserCard({ name, age, email }) {
return (
<div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}>
<h3>{name}</h3>
<p>年龄: {age}</p>
<p>邮箱: {email}</p>
</div>
);
}
以下是使用组件的常见几种方式。
(1)基本使用方式--直接传递
javascript
//.jsx
// Props 本质上就是一个 JavaScript 对象
// 父组件
function App() {
return (
<div>
<UserCard name="张三" age={25} email="zhangsan@example.com" />
<UserCard name="李四" age={30} email="lisi@example.com" />
<UserCard name="王五" age={28} email="wangwu@example.com" />
</div>
);
}
(2)使用变量/对象传递
javascript
function App() {
// 定义用户数据
const user1 = {
name: "张三",
age: 25,
email: "zhangsan@example.com"
};
const userName = "李四";
const userAge = 30;
const userEmail = "lisi@example.com";
return (
<div>
{/* 使用对象展开传递 */}
<UserCard {...user1} />
{/* 使用变量传递 */}
<UserCard name={userName} age={userAge} email={userEmail} />
</div>
);
}
(3)使用数组动态渲染多个组件
javascript
function App() {
// 用户数据数组
const users = [
{ id: 1, name: "张三", age: 25, email: "zhangsan@example.com" },
{ id: 2, name: "李四", age: 30, email: "lisi@example.com" },
{ id: 3, name: "王五", age: 28, email: "wangwu@example.com" },
{ id: 4, name: "赵六", age: 35, email: "zhaoliu@example.com" }
];
return (
<div>
<h1>用户列表</h1>
{users.map(user => (
<UserCard
key={user.id}
name={user.name}
age={user.age}
email={user.email}
/>
))}
</div>
);
}
(4)使用useState管理数据
首先来说一下useState的概念。useState 是React的一个"钩子",它的作用就是让函数组件能够拥有自己的"内部记忆"(即状态)。当状态改变时,React会自动重新渲染页面,显示最新的数据。
bash
const [users, setUsers] = useState(初始值);
users:你定义的"状态变量",就像你给组件的一个"盒子",里面装着当前的数据。
setUsers:一个"更新函数",它是改变"盒子"里数据的唯一合法途径。
useState(...):React给你的"魔法",用来初始化这个盒子。
javascript
import { useState } from 'react';
function App() {
const [users, setUsers] = useState([
{ id: 1, name: "张三", age: 25, email: "zhangsan@example.com" },
{ id: 2, name: "李四", age: 30, email: "lisi@example.com" }
]);
// 添加新用户
const addUser = () => {
const newUser = {
id: users.length + 1,
name: "新用户",
age: 20,
email: "newuser@example.com"
};
setUsers([...users, newUser]);
};
return (
<div>
<button onClick={addUser}>添加用户</button>
{users.map(user => (
<UserCard
key={user.id}
name={user.name}
age={user.age}
email={user.email}
/>
))}
</div>
);
}
上述代码中的"setUsers(...users, newUser)"是React中最经典的数组更新模式。它做了两件事:
(1)创建一个新数组:...users, newUser
(2)用新数组替换旧数组:setUsers(新数组)
为什么不能直接 setUsers(users.push(newUser))?
这是最关键的React原则:永远不要直接修改状态。
users.push(newUser):这会直接修改 users 原数组。React无法检测到这种"偷偷摸摸"的修改,因此不会更新页面。
...users, newUser:这是创建一个全新的数组,原数组纹丝不动。React能清晰地看到"这个变量指向了一个新东西",所以知道需要刷新页面。
bash
[...users, newUser]
这是JavaScript的展开语法。假设原来的 users 数组长这样:
javascript
// 原来的 users 数组
[
{ id: 1, name: "张三", age: 25, email: "..."},
{ id: 2, name: "李四", age: 30, email: "..."}
]
执行 ...users, newUser 的过程就像在"组装"一个新的数组:
(1)...users:把原来的"张三"和"李四"两个对象,像拆盒子一样拿出来,放进新数组。
(2), newUser:紧接着,把新创建的"新用户"对象也放进去。
最终,你得到的是一个全新的数组:
javascript
// 全新的数组,包含3个元素
[
{ id: 1, name: "张三", age: 25, ...},
{ id: 2, name: "李四", age: 30, ...},
{ id: users.length+1, name: "新用户", age: 20, ...} // 新用户被追加在末尾
]
useState的3种常见用法:

3.1.2 在 JSX 中使用 JavaScript
javascript
//.jsx文件
function Profile({ user }) {
const name = "张三"
const age = 25
const isActive = true
const users = ['李雷', '韩梅梅', 'Lily']
return (
<div>
{/* 显示变量 */}
<h1>{name}</h1>
<p>年龄: {age}</p>
{/* 三元运算符 */}
<span>{isActive ? '在线' : '离线'}</span>
{/* 函数调用 */}
<p>{new Date().toLocaleDateString()}</p>
{/* 数组渲染 */}
<ul>
{users.map(user => <li key={user}>{user}</li>)}
</ul>
{/* 条件渲染 */}
{isActive && <button>发送消息</button>}
</div>
)
}
用一个完整案例说明
定义子组件及其样式:

javascript
// UserCard.jsx
import React, { useState } from 'react';
import './UserCard.css';
const UserCard = ({ user }) => {
const [isExpanded, setIsExpanded] = useState(false);
const [likes, setLikes] = useState(0);
const handleToggle = () => {
setIsExpanded(!isExpanded);
};
const handleLike = () => {
setLikes(likes + 1);
};
return (
<div className="user-card">
<div className="card-header">
<img src={user.avatar} alt={user.name} className="avatar" />
<div className="user-info">
<h2>{user.name}</h2>
<p className="email">{user.email}</p>
</div>
</div>
<div className="card-content">
<p><strong>职位:</strong>{user.position}</p>
<p><strong>公司:</strong>{user.company}</p>
{isExpanded && (
<div className="expanded-info">
<p><strong>地点:</strong>{user.location}</p>
<p><strong>技能:</strong>{user.skills.join(', ')}</p>
<p><strong>个人简介:</strong>{user.bio}</p>
</div>
)}
</div>
<div className="card-actions">
<button onClick={handleToggle} className="btn-toggle">
{isExpanded ? '收起' : '展开'}
</button>
<button onClick={handleLike} className="btn-like">
👍 {likes} 点赞
</button>
</div>
</div>
);
};
export default UserCard;
css
/* UserCard.css */
.user-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin: 15px;
max-width: 400px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.user-card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
transform: translateY(-2px);
}
.card-header {
display: flex;
align-items: center;
margin-bottom: 15px;
}
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
margin-right: 15px;
object-fit: cover;
}
.user-info h2 {
margin: 0 0 5px 0;
color: #333;
}
.email {
color: #666;
font-size: 14px;
margin: 0;
}
.card-content {
margin: 15px 0;
line-height: 1.6;
}
.expanded-info {
margin-top: 10px;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
animation: slideDown 0.3s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card-actions {
display: flex;
gap: 10px;
margin-top: 15px;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
}
.btn-toggle {
background-color: #4CAF50;
color: white;
}
.btn-toggle:hover {
background-color: #45a049;
}
.btn-like {
background-color: #f44336;
color: white;
}
.btn-like:hover {
background-color: #da190b;
}
父组件使用定义好的子组件:

javascript
// App.jsx
import React, { useState } from 'react';
import UserCard from './UserCard';
const App = () => {
const [users, setUsers] = useState([
{
id: 1,
name: '张三',
email: 'zhangsan@example.com',
position: '前端开发工程师',
company: '科技公司',
location: '北京',
skills: ['React', 'Vue', 'JavaScript', 'CSS'],
bio: '5年前端开发经验,热爱开源技术',
avatar: 'https://via.placeholder.com/80/4CAF50/white?text=ZS'
},
{
id: 2,
name: '李四',
email: 'lisi@example.com',
position: 'UI设计师',
company: '设计工作室',
location: '上海',
skills: ['Figma', 'Sketch', 'Adobe XD', 'UI/UX'],
bio: '追求完美的设计,用户体验至上',
avatar: 'https://via.placeholder.com/80/2196F3/white?text=LS'
},
{
id: 3,
name: '王五',
email: 'wangwu@example.com',
position: '全栈开发',
company: '互联网公司',
location: '深圳',
skills: ['React', 'Node.js', 'Python', 'MongoDB'],
bio: '全栈开发者,热爱学习和分享',
avatar: 'https://via.placeholder.com/80/FF9800/white?text=WW'
}
]);
const addUser = () => {
const newUser = {
id: users.length + 1,
name: `新用户${users.length + 1}`,
email: `user${users.length + 1}@example.com`,
position: '新入职员工',
company: '新公司',
location: '广州',
skills: ['学习中'],
bio: '新成员,请多关照',
avatar: `https://via.placeholder.com/80/9C27B0/white?text=NU${users.length + 1}`
};
setUsers([...users, newUser]);
};
const removeUser = (id) => {
setUsers(users.filter(user => user.id !== id));
};
return (
<div style={{ padding: '20px', maxWidth: '1200px', margin: '0 auto' }}>
<h1>用户信息展示</h1>
<button
onClick={addUser}
style={{
marginBottom: '20px',
padding: '10px 20px',
backgroundColor: '#2196F3',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
添加新用户
</button>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(400px, 1fr))', gap: '20px' }}>
{users.map(user => (
<div key={user.id} style={{ position: 'relative' }}>
<UserCard user={user} />
<button
onClick={() => removeUser(user.id)}
style={{
position: 'absolute',
top: '10px',
right: '10px',
backgroundColor: '#f44336',
color: 'white',
borderRadius: '50%',
width: '30px',
height: '30px',
padding: '0',
fontSize: '16px'
}}
>
×
</button>
</div>
))}
</div>
{users.length === 0 && (
<p style={{ textAlign: 'center', color: '#999' }}>暂无用户数据</p>
)}
</div>
);
};
export default App;
看看效果:


运行后你将看到:
3个默认用户卡片,每个卡片显示头像、姓名、邮箱和职位
点击"展开"按钮显示更多信息(地点、技能、个人简介)
点击"点赞"按钮增加点赞数
点击"添加新用户"按钮动态添加新卡片
点击右上角"×"删除卡片
卡片有悬停动画效果
响应式网格布局
3.2 条件渲染
5种条件渲染方式。
javascript
function ConditionalRender({ isLoggedIn, user, loading }) {
// 1. if 语句(适合复杂逻辑)
if (loading) {
return <div>加载中...</div>
}
// 2. 三元运算符(简单条件)
return (
<div>
{isLoggedIn ? (
<div>
<h1>欢迎回来, {user.name}!</h1>
<button>登出</button>
</div>
) : (
<div>
<h1>请先登录</h1>
<button>登录</button>
</div>
)}
{/* 3. && 运算符(只显示或什么都不显示)*/}
{isLoggedIn && <Notification />}
{/* 4. 变量存储(适合多条件)*/}
{(() => {
if (user.vip) return <VIPBadge />
if (user.new) return <NewBadge />
return <NormalBadge />
})()}
{/* 5. 立即执行函数 */}
{(() => {
switch(user.level) {
case 'gold': return <GoldBadge />
case 'silver': return <SilverBadge />
default: return <NormalBadge />
}
})()}
</div>
)
}
3.3 事件处理
常用事件和用法
javascript
function EventDemo() {
// 1. 点击事件
const handleClick = () => {
alert('按钮被点击了')
}
// 2. 带参数的事件
const handleDelete = (id) => {
console.log('删除:', id)
}
// 3. 表单事件
const handleSubmit = (e) => {
e.preventDefault() // 阻止默认行为
console.log('表单提交')
}
// 4. 键盘事件
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
console.log('按下了回车')
}
}
// 5. 鼠标事件
const handleMouseEnter = () => {
console.log('鼠标进入')
}
return (
<div>
{/* 基本点击 */}
<button onClick={handleClick}>点击我</button>
{/* 带参数 */}
<button onClick={() => handleDelete(1)}>删除</button>
{/* 表单提交 */}
<form onSubmit={handleSubmit}>
<input
onKeyPress={handleKeyPress}
onMouseEnter={handleMouseEnter}
/>
<button type="submit">提交</button>
</form>
{/* 阻止冒泡 */}
<div onClick={() => console.log('外层')}>
<button onClick={(e) => {
e.stopPropagation()
console.log('内层')
}}>
点击(不冒泡)
</button>
</div>
</div>
)
}
3.4 常用Hooks
Hooks的作用是在不编写 class组件 的情况下使用 state 和其他 React 特性。
3.4.1 Hooks的核心规则
1. 只在顶层调用 Hooks
javascript
// ❌ 错误:不能在条件语句中
function Component({ cond }) {
if (cond) {
const [state, setState] = useState(0) // 违反规则!
}
}
// ❌ 错误:不能在循环中
function Component() {
for (let i = 0; i < 3; i++) {
const [state, setState] = useState(0) // 违反规则!
}
}
// ✅ 正确:始终在顶层
function Component({ cond }) {
const [state, setState] = useState(0)
if (cond) {
// 可以使用 state,但不能定义 Hook
}
}
2. 只在 React 函数中调用 Hooks
javascript
// ✅ 在 Function 组件中
function MyComponent() {
const [state, setState] = useState(0)
}
// ✅ 在自定义 Hook 中
function useCustomHook() {
const [state, setState] = useState(0)
}
// ❌ 普通 JavaScript 函数
function normalFunction() {
const [state, setState] = useState(0) // 错误!
}
3.4.2 内置Hooks
1. useEffect - 副作用处理
javascript
import { useState, useEffect } from 'react'
function DataFetching() {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
// 组件挂载时执行一次
useEffect(() => {
fetchData()
}, []) // 空数组 = 只执行一次
// 监听某个状态变化
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `点击了 ${count} 次`
}, [count]) // count 变化时执行
// 清理副作用(如定时器)
useEffect(() => {
const timer = setInterval(() => {
console.log('定时器执行')
}, 1000)
return () => {
clearInterval(timer) // 组件卸载时清理
}
}, [])
const fetchData = async () => {
setLoading(true)
const res = await fetch('https://api.example.com/data')
const json = await res.json()
setData(json)
setLoading(false)
}
if (loading) return <div>加载中...</div>
return <div>{JSON.stringify(data)}</div>
}
2. useContext - 跨组件传值
javascript
import { createContext, useContext, useState } from 'react'
// 创建 Context
const ThemeContext = createContext()
// 父组件提供数据
function App() {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
)
}
// 中间组件不需要传 props
function Toolbar() {
return <ThemedButton />
}
// 深层组件直接使用
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext)
return (
<button
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#333' : '#fff'
}}
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
切换主题
</button>
)
}
3. useRef - 引用 DOM 或存储值
javascript
import { useRef, useEffect } from 'react'
function RefDemo() {
// 引用 DOM 元素
const inputRef = useRef(null)
// 存储不触发重新渲染的值
const countRef = useRef(0)
const focusInput = () => {
inputRef.current.focus()
}
const incrementRef = () => {
countRef.current++
console.log('Ref count:', countRef.current)
// 不会触发组件重新渲染
}
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>聚焦输入框</button>
<button onClick={incrementRef}>增加 Ref</button>
</div>
)
}