3. 前端基础(二):React Hooks(useState、useEffect)、事件处理、条件渲染与列表渲染
好,咱们接着聊。上一章我们把 React 的环境搭好了,也写了个最简单的组件。这一章,咱们要真正开始写点有“交互”的东西了。
说白了,前端页面不就是“数据 + 事件 + 界面”这三样吗?数据变了,界面跟着变;用户点了按钮,触发事件,数据再变。React 里怎么玩这套?核心就是 Hooks。
3.1 状态管理:useState
先问个问题:你写了一个变量 let count = 0,然后在页面上显示它。用户点了个按钮,你执行 count++,页面会更新吗?
答案是:不会。React 不知道这个变量变了。你得用 React 的方式来声明“状态”。
useState 就是干这个的。它让函数组件拥有了“记忆”能力。
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>你点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>点我</button>
</div>
);
}
这里有个小细节:useState(0) 里的 0 是初始值。count 是当前值,setCount 是更新函数。你只能通过 setCount 来改 count,直接 count++ 是没用的。
user 和 setUser,loading 和 setLoading。这样一眼就能看出哪个是状态,哪个是更新函数。
3.2 副作用处理:useEffect
有了状态,我们还得处理“副作用”。什么是副作用?比如:发请求、操作 DOM、设置定时器。这些事不能写在组件渲染的主流程里,否则会出大问题。
useEffect 就是用来处理这些的。它会在组件渲染完成后执行。
import React, { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// 这里发请求
fetch(`/api/user/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]); // 依赖数组
if (!user) return <p>加载中...</p>;
return <p>用户名:{user.name}</p>;
}
注意第二个参数 [userId],这叫“依赖数组”。它的意思是:只有当 userId 变了,才重新执行这个 effect。如果传空数组 [],那就只在组件挂载时执行一次。
3.3 事件处理
React 里的事件处理,跟原生 DOM 有点像,但写法更简洁。比如点击事件:
function Button() {
const handleClick = (e) => {
console.log('按钮被点击了', e);
};
return <button onClick={handleClick}>点我</button>;
}
注意几点:
- 事件名用驼峰命名:
onClick、onChange、onSubmit - 传的是函数引用,不是调用结果:
onClick={handleClick}而不是onClick={handleClick()} - 事件对象
e是合成事件,跨浏览器兼容
我建议把事件处理函数单独抽出来,别在 JSX 里写箭头函数。一来可读性好,二来避免每次渲染都创建新函数。
3.4 条件渲染
条件渲染说白了就是“根据状态显示不同的内容”。React 里没有特殊的语法,直接用 JavaScript 的 if 或三元运算符就行。
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>欢迎回来!</h1>;
}
return <h1>请先登录</h1>;
}
更常见的写法是三元表达式:
function Status({ loading, error }) {
return (
<div>
{loading ? <p>加载中...</p> :
error ? <p>出错了:{error}</p> :
<p>数据加载完成</p>}
</div>
);
}
还有一种情况:只显示或隐藏某个元素,可以用 && 运算符:
{unreadCount > 0 && <span>你有 {unreadCount} 条未读消息</span>}
0 && <Component /> 这种写法,因为 0 会被渲染出来。我一般写成 count > 0 && ... 或者用 !!count 转成布尔值。
3.5 列表渲染
列表渲染,核心就是 map 方法。把数组映射成一组 JSX 元素。
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
这里有个关键点:key。每个列表项必须有一个唯一的 key,React 用它来识别哪些元素变了、哪些需要重新渲染。
| key 的选择 | 推荐吗? | 原因 |
|---|---|---|
| 数据中的 id | ✅ 推荐 | 稳定、唯一 |
| 数组索引 index | ⚠️ 不推荐 | 列表顺序变化时会导致性能问题或 bug |
| 随机数 | ❌ 不要用 | 每次渲染都变,失去缓存意义 |
3.6 综合示例:一个简单的待办事项
把上面这些知识点串起来,写个简单的 Todo 应用:
import React, { useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim() === '') return;
setTodos([...todos, { id: Date.now(), text: input, done: false }]);
setInput('');
};
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};
return (
<div>
<h2>我的待办</h2>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="输入待办事项"
/>
<button onClick={addTodo}>添加</button>
<ul>
{todos.length === 0 ? (
<li>暂无待办事项</li>
) : (
todos.map(todo => (
<li
key={todo.id}
onClick={() => toggleTodo(todo.id)}
style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
>
{todo.text}
</li>
))
)}
</ul>
</div>
);
}
这个例子涵盖了:useState 管理输入框和列表、事件处理(点击、输入)、条件渲染(空列表提示)、列表渲染(map + key)。你想想看,是不是把这一章的知识点都用上了?
好,这一章就到这。下一章我们会深入聊聊组件通信和表单处理,到时候你会发现,这些基础 Hooks 组合起来能玩出很多花样。