这是一份完整的 React18 学习笔记,涵盖从基础到进阶的所有核心知识点。
学习路线图
1
| 基础入门 → Hooks 深入 → 组件通信 → TypeScript → 全家桶 → 性能优化 → React18 新特性
|
一、React 基础入门
1.1 项目搭建
创建 React 项目:
1 2 3 4 5 6 7
| npx create-react-app my-app cd my-app npm start
npx create-react-app my-app --template typescript
|
项目结构:
1 2 3 4 5 6 7 8 9
| my-app/ ├── public/ # 静态资源 │ └── index.html ├── src/ │ ├── index.js # 入口文件 │ ├── App.js # 根组件 │ └── ... ├── package.json └── README.md
|
1.2 React 18 入口文件
1 2 3 4 5 6 7 8 9 10 11
| import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <App /> </React.StrictMode> );
|
1.3 JSX 语法
JSX 是 JavaScript 的语法扩展,可以在 JS 中写 HTML。
基本规则:
- 只能有一个根元素(可用
<></> 或 <div> 包裹)
- 标签必须闭合
- 使用
{} 插入 JavaScript 表达式
class → className,for → htmlFor
style 接受对象:style={{ color: 'red' }}
常见用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function App() { const name = 'React'; const age = 18; const isLogin = true; return ( <div className="app"> {/* 1. 变量 */} <h1>Hello {name}</h1> {/* 2. 表达式 */} <p>明年 {age + 1} 岁</p> {/* 3. 函数调用 */} <p>{getName()}</p> {/* 4. 条件渲染 - 逻辑与 */} {isLogin && <span>已登录</span>} {/* 5. 条件渲染 - 三元运算符 */} {isLogin ? <span>欢迎回来</span> : <span>请登录</span>} {/* 6. 样式对象 */} <div style={{ color: 'red', fontSize: '20px' }}> 红色文字 </div> </div> ); }
|
列表渲染:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function UserList() { const users = [ { id: 1, name: '张三', age: 20 }, { id: 2, name: '李四', age: 25 }, { id: 3, name: '王五', age: 30 }, ]; return ( <ul> {users.map(user => ( <li key={user.id}> {user.name} - {user.age}岁 </li> ))} </ul> ); }
|
注意: key 应该使用稳定、唯一的 ID,不推荐使用数组索引。
1.4 事件处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function EventDemo() { const handleClick = (e) => { console.log('点击了', e); }; const handleDelete = (id, e) => { console.log('删除', id, e); }; const handleSubmit = (e) => { e.preventDefault(); console.log('提交表单'); }; return ( <div> <button onClick={handleClick}>点击</button> <button onClick={(e) => handleDelete(123, e)}>删除</button> <form onSubmit={handleSubmit}> <button type="submit">提交</button> </form> </div> ); }
|
二、React Hooks 核心
2.1 useState - 状态管理
useState 是最基础的 Hook,用于在函数组件中添加状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { useState } from 'react';
function Counter() { const [count, setCount] = useState(0); return ( <div> <p>计数:{count}</p> <button onClick={() => setCount(count + 1)}>+1</button> <button onClick={() => setCount(count - 1)}>-1</button> <button onClick={() => setCount(0)}>重置</button> </div> ); }
|
重要规则:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const [user, setUser] = useState({ name: 'Jack' }); user.name = 'Tom';
setUser({ ...user, name: 'Tom' });
const [list, setList] = useState([1, 2, 3]); list.push(4);
setList([...list, 4]);
|
惰性初始化:
1 2 3 4 5 6 7
|
const [state, setState] = useState(() => { const initialValue = expensiveComputation(); return initialValue; });
|
2.2 useEffect - 副作用处理
useEffect 用于处理副作用(数据获取、订阅、DOM 操作等)。
什么是副作用?
副作用是指组件渲染之外的操作,比如:
- 发送网络请求
- 操作 DOM
- 设置定时器
- 订阅事件
- 修改全局变量
基础用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import { useEffect, useState } from 'react';
function Example() { const [count, setCount] = useState(0); useEffect(() => { console.log('组件渲染了'); }); useEffect(() => { console.log('组件挂载了'); return () => { console.log('组件卸载了'); }; }, []); useEffect(() => { console.log('count 变化了:', count); }, [count]); return ( <div> <p>{count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ); }
|
常见应用场景详解:
场景1:数据获取(最常用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchUser = async () => { try { setLoading(true); setError(null); const response = await fetch(`/api/users/${userId}`); const data = await response.json(); setUser(data); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchUser(); }, [userId]); if (loading) return <div>加载中...</div>; if (error) return <div>错误:{error}</div>; if (!user) return <div>用户不存在</div>; return ( <div> <h2>{user.name}</h2> <p>{user.email}</p> </div> ); }
|
场景2:订阅和取消订阅(防止内存泄漏)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| function ChatRoom({ roomId }) { const [messages, setMessages] = useState([]); useEffect(() => { const socket = new WebSocket(`ws://localhost:3000/chat/${roomId}`); socket.onmessage = (event) => { const newMessage = JSON.parse(event.data); setMessages(prev => [...prev, newMessage]); }; return () => { console.log('关闭连接'); socket.close(); }; }, [roomId]); return ( <div> <h2>聊天室 {roomId}</h2> <ul> {messages.map((msg, index) => ( <li key={index}>{msg.text}</li> ))} </ul> </div> ); }
|
场景3:定时器(必须清理)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| function Timer() { const [seconds, setSeconds] = useState(0); const [isRunning, setIsRunning] = useState(false); useEffect(() => { if (!isRunning) return; const timer = setInterval(() => { setSeconds(prev => prev + 1); }, 1000); return () => { console.log('清除定时器'); clearInterval(timer); }; }, [isRunning]); return ( <div> <p>已运行:{seconds} 秒</p> <button onClick={() => setIsRunning(!isRunning)}> {isRunning ? '暂停' : '开始'} </button> <button onClick={() => setSeconds(0)}>重置</button> </div> ); }
|
场景4:DOM 操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function AutoFocusInput() { const [value, setValue] = useState(''); useEffect(() => { const input = document.getElementById('my-input'); input.focus(); }, []); return ( <input id="my-input" value={value} onChange={(e) => setValue(e.target.value)} /> ); }
|
场景5:监听窗口事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| function WindowSize() { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight, }); useEffect(() => { const handleResize = () => { setSize({ width: window.innerWidth, height: window.innerHeight, }); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); }; }, []); return ( <div> <p>窗口宽度:{size.width}px</p> <p>窗口高度:{size.height}px</p> </div> ); }
|
场景6:依赖多个值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| function SearchResults({ query, category }) { const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { if (!query) { setResults([]); return; } const fetchResults = async () => { setLoading(true); try { const response = await fetch( `/api/search?q=${query}&category=${category}` ); const data = await response.json(); setResults(data); } catch (error) { console.error(error); } finally { setLoading(false); } }; fetchResults(); }, [query, category]); if (loading) return <div>搜索中...</div>; return ( <ul> {results.map(item => ( <li key={item.id}>{item.title}</li> ))} </ul> ); }
|
场景7:防抖搜索(避免频繁请求)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| function SearchInput() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); useEffect(() => { if (!query) { setResults([]); return; } const timer = setTimeout(() => { console.log('发送搜索请求:', query); fetch(`/api/search?q=${query}`) .then(res => res.json()) .then(data => setResults(data)); }, 500); return () => { clearTimeout(timer); }; }, [query]); return ( <div> <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="输入搜索内容" /> <ul> {results.map(item => ( <li key={item.id}>{item.title}</li> ))} </ul> </div> ); }
|
场景8:localStorage 同步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function ThemeToggle() { const [theme, setTheme] = useState(() => { return localStorage.getItem('theme') || 'light'; }); useEffect(() => { localStorage.setItem('theme', theme); document.body.className = theme; }, [theme]); return ( <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> 切换主题(当前:{theme}) </button> ); }
|
常见错误和注意事项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| useEffect(() => { fetchData(userId); }, []);
useEffect(() => { fetchData(userId); }, [userId]);
useEffect(() => { count = count + 1; }, []);
useEffect(() => { setCount(prev => prev + 1); }, []);
useEffect(() => { const timer = setInterval(() => { console.log('tick'); }, 1000); }, []);
useEffect(() => { const timer = setInterval(() => { console.log('tick'); }, 1000); return () => clearInterval(timer); }, []);
useEffect(async () => { const data = await fetchData(); }, []);
useEffect(() => { const fetchData = async () => { const data = await fetch('/api/data'); }; fetchData(); }, []);
|
执行顺序示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| function LifecycleDemo() { const [count, setCount] = useState(0); console.log('1. 组件渲染'); useEffect(() => { console.log('2. useEffect 执行(无依赖)'); }); useEffect(() => { console.log('3. useEffect 执行(空依赖)'); return () => { console.log('4. 清理函数执行(组件卸载时)'); }; }, []); useEffect(() => { console.log('5. useEffect 执行(count 依赖)', count); return () => { console.log('6. 清理函数执行(count 变化前)'); }; }, [count]); return ( <button onClick={() => setCount(count + 1)}> 点击 {count} 次 </button> ); }
|
2.3 useContext - 跨组件通信
useContext 用于跨层级传递数据,避免 props 逐层传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(); const UserContext = createContext();
function App() { const [theme, setTheme] = useState('light'); const [user, setUser] = useState({ name: '张三' }); return ( <ThemeContext.Provider value={{ theme, setTheme }}> <UserContext.Provider value={{ user, setUser }}> {/* 所有子组件都可以访问这些数据,无需通过 props 逐层传递 */} <Header /> <Content /> </UserContext.Provider> </ThemeContext.Provider> ); }
function Header() { const { theme, setTheme } = useContext(ThemeContext); const { user } = useContext(UserContext); return ( <header> <p>当前主题:{theme}</p> <p>用户:{user.name}</p> {/* 可以直接修改顶层的状态 */} <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> 切换主题 </button> </header> ); }
|
2.4 useReducer - 复杂状态管理
useReducer 适用于复杂的状态逻辑,类似 Redux。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import { useReducer } from 'react';
function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; case 'reset': return { count: 0 }; default: throw new Error('Unknown action'); } }
function Counter() { const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( <div> <p>计数:{state.count}</p> {/* 调用 dispatch 并传入 action 对象来更新状态 */} <button onClick={() => dispatch({ type: 'increment' })}>+1</button> <button onClick={() => dispatch({ type: 'decrement' })}>-1</button> <button onClick={() => dispatch({ type: 'reset' })}>重置</button> </div> ); }
|
2.5 useRef - DOM 引用与持久化值
useRef 有两个主要用途:
- 获取 DOM 元素
- 保存跨渲染的可变值(不触发重新渲染)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import { useRef, useEffect } from 'react';
function TextInput() { const inputRef = useRef(null); const countRef = useRef(0); useEffect(() => { inputRef.current.focus(); }, []); const handleClick = () => { console.log(inputRef.current.value); countRef.current += 1; console.log('点击次数:', countRef.current); }; return ( <div> {/* 将 ref 绑定到 DOM 元素 */} <input ref={inputRef} type="text" /> <button onClick={handleClick}>提交</button> </div> ); }
|
2.6 useMemo - 缓存计算结果
useMemo 用于缓存计算结果,避免每次渲染都重新计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { useMemo, useState } from 'react';
function ExpensiveComponent({ items }) { const [count, setCount] = useState(0); const total = useMemo(() => { console.log('计算总价...'); return items.reduce((sum, item) => sum + item.price, 0); }, [items]); return ( <div> <p>总价:¥{total}</p> <p>计数:{count}</p> {/* 点击这个按钮不会触发 total 重新计算 */} <button onClick={() => setCount(count + 1)}>+1</button> </div> ); }
|
2.7 useCallback - 缓存函数
useCallback 用于缓存函数引用,避免子组件不必要的重新渲染。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import { useCallback, useState, memo } from 'react';
const Button = memo(({ onClick, children }) => { console.log('Button 渲染'); return <button onClick={onClick}>{children}</button>; });
function Parent() { const [count, setCount] = useState(0); const [text, setText] = useState(''); const handleClick = useCallback(() => { console.log('点击了', count); }, [count]); return ( <div> {/* 输入文字时,text 变化,Parent 重新渲染 */} <input value={text} onChange={(e) => setText(e.target.value)} /> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>+1</button> {/* 因为 handleClick 被缓存了,输入文字时 Button 不会重新渲染 */} <Button onClick={handleClick}>子组件按钮</Button> </div> ); }
|
2.8 自定义 Hook
自定义 Hook 用于复用状态逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| function useLocalStorage(key, initialValue) { const [value, setValue] = useState(() => { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : initialValue; }); useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); return [value, setValue]; }
function App() { const [name, setName] = useLocalStorage('name', ''); return ( <input value={name} onChange={(e) => setName(e.target.value)} placeholder="输入姓名(会自动保存)" /> ); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setLoading(true); const response = await fetch(url); const json = await response.json(); setData(json); } catch (err) { setError(err); } finally { setLoading(false); } }; fetchData(); }, [url]); return { data, loading, error }; }
function UserList() { const { data, loading, error } = useFetch('/api/users'); if (loading) return <div>加载中...</div>; if (error) return <div>错误:{error.message}</div>; return ( <ul> {data.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }
|
Hook 使用规则:
- 只在函数组件最外层调用 Hook
- 不要在循环、条件或嵌套函数中调用
- 只在 React 函数组件或自定义 Hook 中调用
三、组件通信
3.1 父 → 子:Props
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function Parent() { const user = { name: '张三', age: 20 }; return <Child user={user} onUpdate={handleUpdate} />; }
function Child({ user, onUpdate }) { return ( <div> <p>{user.name} - {user.age}岁</p> <button onClick={onUpdate}>更新</button> </div> ); }
|
3.2 子 → 父:回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| function Parent() { const [message, setMessage] = useState(''); const handleMessage = (msg) => { setMessage(msg); }; return ( <div> <p>来自子组件:{message}</p> <Child onSend={handleMessage} /> </div> ); }
function Child({ onSend }) { const [input, setInput] = useState(''); const handleClick = () => { onSend(input); }; return ( <div> <input value={input} onChange={(e) => setInput(e.target.value)} /> <button onClick={handleClick}>发送</button> </div> ); }
|
3.3 跨层级:Context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const MessageContext = createContext();
function App() { const [message, setMessage] = useState('Hello'); return ( <MessageContext.Provider value={{ message, setMessage }}> <Parent /> </MessageContext.Provider> ); }
function Parent() { return <Child />; }
function Child() { const { message, setMessage } = useContext(MessageContext); return ( <div> <p>{message}</p> <button onClick={() => setMessage('World')}>改变</button> </div> ); }
|
3.4 插槽(children)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| function Card({ children, title }) { return ( <div className="card"> <h2>{title}</h2> <div className="content">{children}</div> </div> ); }
<Card title="标题"> <p>这是内容</p> <button>按钮</button> </Card>
function Layout({ header, footer, children }) { return ( <div> <header>{header}</header> <main>{children}</main> <footer>{footer}</footer> </div> ); }
<Layout header={<h1>页头</h1>} footer={<p>页脚</p>} > <p>主要内容</p> </Layout>
|
四、表单处理
4.1 受控组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| function Form() { const [formData, setFormData] = useState({ username: '', email: '', gender: 'male', hobbies: [], agree: false, }); const handleChange = (e) => { const { name, value, type, checked } = e.target; setFormData({ ...formData, [name]: type === 'checkbox' ? checked : value, }); }; const handleSubmit = (e) => { e.preventDefault(); console.log('提交:', formData); }; return ( <form onSubmit={handleSubmit}> {/* 文本输入:value 绑定状态,onChange 更新状态 */} <input name="username" value={formData.username} onChange={handleChange} placeholder="用户名" /> {/* 邮箱输入 */} <input name="email" type="email" value={formData.email} onChange={handleChange} placeholder="邮箱" /> {/* 下拉选择:value 绑定当前选中的值 */} <select name="gender" value={formData.gender} onChange={handleChange}> <option value="male">男</option> <option value="female">女</option> </select> {/* 复选框:checked 绑定布尔值 */} <label> <input name="agree" type="checkbox" checked={formData.agree} onChange={handleChange} /> 同意条款 </label> <button type="submit">提交</button> </form> ); }
|
五、TypeScript 集成
5.1 基础类型定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| interface UserProps { name: string; age: number; email?: string; onUpdate: (name: string) => void; }
function User({ name, age, email, onUpdate }: UserProps) { return ( <div> <p>{name} - {age}岁</p> {email && <p>{email}</p>} <button onClick={() => onUpdate('新名字')}>更新</button> </div> ); }
|
5.2 useState 类型
1 2 3 4 5 6 7 8 9 10 11 12 13
| const [count, setCount] = useState<number>(0); const [name, setName] = useState<string>('');
interface User { id: number; name: string; email: string; }
const [user, setUser] = useState<User | null>(null); const [users, setUsers] = useState<User[]>([]);
|
5.3 useRef 类型
1 2 3 4 5 6
| const inputRef = useRef<HTMLInputElement>(null); const divRef = useRef<HTMLDivElement>(null);
const countRef = useRef<number>(0);
|
5.4 事件类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function Form() { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { console.log(e.target.value); }; const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); }; const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => { console.log('点击'); }; return ( <form onSubmit={handleSubmit}> <input onChange={handleChange} /> <button onClick={handleClick}>提交</button> </form> ); }
|
5.5 完整示例:Todo App
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| interface Todo { id: number; content: string; completed: boolean; }
function TodoApp() { const [todos, setTodos] = useState<Todo[]>([]); const [input, setInput] = useState<string>(''); const addTodo = () => { if (!input.trim()) return; const newTodo: Todo = { id: Date.now(), content: input, completed: false, }; setTodos([...todos, newTodo]); setInput(''); }; const toggleTodo = (id: number) => { setTodos( todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo ) ); }; const deleteTodo = (id: number) => { setTodos(todos.filter(todo => todo.id !== id)); }; return ( <div> <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="输入待办事项" /> <button onClick={addTodo}>添加</button> <ul> {todos.map(todo => ( <li key={todo.id}> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none', }} > {todo.content} </span> <button onClick={() => toggleTodo(todo.id)}> {todo.completed ? '未完成' : '完成'} </button> <button onClick={() => deleteTodo(todo.id)}>删除</button> </li> ))} </ul> </div> ); }
|
六、React Router - 路由管理
6.1 安装与基础配置
1
| npm install react-router-dom
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() { return ( <BrowserRouter> <nav> <Link to="/">首页</Link> <Link to="/about">关于</Link> <Link to="/users">用户</Link> </nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="/users" element={<Users />} /> <Route path="/users/:id" element={<UserDetail />} /> <Route path="*" element={<NotFound />} /> </Routes> </BrowserRouter> ); }
|
6.2 编程式导航
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { useNavigate } from 'react-router-dom';
function Home() { const navigate = useNavigate(); const goToAbout = () => { navigate('/about'); }; const goToUser = (id) => { navigate(`/users/${id}`); }; const goBack = () => { navigate(-1); }; return ( <div> <button onClick={goToAbout}>跳转到关于</button> <button onClick={() => goToUser(123)}>查看用户123</button> <button onClick={goBack}>返回</button> </div> ); }
|
6.3 获取路由参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { useParams, useSearchParams } from 'react-router-dom';
function UserDetail() { const { id } = useParams(); const [searchParams, setSearchParams] = useSearchParams(); const tab = searchParams.get('tab'); const page = searchParams.get('page'); return ( <div> <p>用户 ID: {id}</p> <p>Tab: {tab}</p> <p>Page: {page}</p> <button onClick={() => setSearchParams({ tab: 'posts', page: '1' })}> 切换参数 </button> </div> ); }
|
6.4 嵌套路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { Outlet } from 'react-router-dom';
function App() { return ( <Routes> <Route path="/" element={<Layout />}> <Route index element={<Home />} /> <Route path="about" element={<About />} /> <Route path="users" element={<Users />}> <Route path=":id" element={<UserDetail />} /> </Route> </Route> </Routes> ); }
function Layout() { return ( <div> <nav>导航栏</nav> <Outlet /> {/* 子路由渲染位置 */} <footer>页脚</footer> </div> ); }
|
6.5 路由守卫(权限控制)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { Navigate } from 'react-router-dom';
function PrivateRoute({ children }) { const isAuthenticated = localStorage.getItem('token'); if (!isAuthenticated) { return <Navigate to="/login" replace />; } return children; }
<Route path="/dashboard" element={ <PrivateRoute> <Dashboard /> </PrivateRoute> } />
|
7.1 安装与配置
1
| npm install @reduxjs/toolkit react-redux
|
1 2 3 4 5 6 7 8 9 10 11
| import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './counterSlice'; import userReducer from './userSlice';
export const store = configureStore({ reducer: { counter: counterReducer, user: userReducer, }, });
|
1 2 3 4 5 6 7 8 9 10 11
| import { Provider } from 'react-redux'; import { store } from './store';
function App() { return ( <Provider store={store}> <Counter /> </Provider> ); }
|
7.2 创建 Slice
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({ name: 'counter', initialState: { value: 0, }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, reset: (state) => { state.value = 0; }, }, });
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions; export default counterSlice.reducer;
|
7.3 在组件中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { useSelector, useDispatch } from 'react-redux'; import { increment, decrement, incrementByAmount } from './store/counterSlice';
function Counter() { const count = useSelector((state) => state.counter.value); const dispatch = useDispatch(); return ( <div> <p>Count: {count}</p> <button onClick={() => dispatch(increment())}>+1</button> <button onClick={() => dispatch(decrement())}>-1</button> <button onClick={() => dispatch(incrementByAmount(5))}>+5</button> </div> ); }
|
7.4 异步操作(createAsyncThunk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const fetchUser = createAsyncThunk( 'user/fetchUser', async (userId) => { const response = await fetch(`/api/users/${userId}`); return response.json(); } );
const userSlice = createSlice({ name: 'user', initialState: { data: null, loading: false, error: null, }, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchUser.pending, (state) => { state.loading = true; state.error = null; }) .addCase(fetchUser.fulfilled, (state, action) => { state.loading = false; state.data = action.payload; }) .addCase(fetchUser.rejected, (state, action) => { state.loading = false; state.error = action.error.message; }); }, });
export default userSlice.reducer;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function UserProfile({ userId }) { const dispatch = useDispatch(); const { data, loading, error } = useSelector((state) => state.user); useEffect(() => { dispatch(fetchUser(userId)); }, [dispatch, userId]); if (loading) return <div>加载中...</div>; if (error) return <div>错误:{error}</div>; return <div>{data?.name}</div>; }
|
八、Axios - HTTP 请求
8.1 安装与基础用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import axios from 'axios';
const getUsers = async () => { try { const response = await axios.get('/api/users'); console.log(response.data); } catch (error) { console.error(error); } };
const createUser = async (userData) => { try { const response = await axios.post('/api/users', userData); console.log(response.data); } catch (error) { console.error(error); } };
const updateUser = async (id, userData) => { await axios.put(`/api/users/${id}`, userData); };
const deleteUser = async (id) => { await axios.delete(`/api/users/${id}`); };
|
8.2 创建实例和拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import axios from 'axios';
const request = axios.create({ baseURL: 'https://api.example.com', timeout: 10000, });
request.interceptors.request.use( (config) => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => { return Promise.reject(error); } );
request.interceptors.response.use( (response) => { return response.data; }, (error) => { if (error.response?.status === 401) { window.location.href = '/login'; } return Promise.reject(error); } );
export default request;
|
8.3 在组件中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import request from './api/request';
function UserList() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { const fetchUsers = async () => { setLoading(true); try { const data = await request.get('/users'); setUsers(data); } catch (error) { console.error(error); } finally { setLoading(false); } }; fetchUsers(); }, []); if (loading) return <div>加载中...</div>; return ( <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }
|
九、React Query - 数据获取
9.1 安装与配置
1
| npm install @tanstack/react-query
|
1 2 3 4 5 6 7 8 9 10 11 12
| import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() { return ( <QueryClientProvider client={queryClient}> <UserList /> </QueryClientProvider> ); }
|
9.2 useQuery - 数据查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { useQuery } from '@tanstack/react-query';
function UserList() { const { data, isLoading, error, refetch } = useQuery({ queryKey: ['users'], queryFn: async () => { const response = await fetch('/api/users'); return response.json(); }, staleTime: 5000, cacheTime: 10000, }); if (isLoading) return <div>加载中...</div>; if (error) return <div>错误:{error.message}</div>; return ( <div> <button onClick={refetch}>刷新</button> <ul> {data.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> ); }
|
9.3 useMutation - 数据修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import { useMutation, useQueryClient } from '@tanstack/react-query';
function CreateUser() { const queryClient = useQueryClient(); const mutation = useMutation({ mutationFn: async (newUser) => { const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newUser), }); return response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); const handleSubmit = (e) => { e.preventDefault(); mutation.mutate({ name: 'New User', email: 'user@example.com' }); }; return ( <form onSubmit={handleSubmit}> <button type="submit" disabled={mutation.isPending}> {mutation.isPending ? '创建中...' : '创建用户'} </button> {mutation.isError && <div>错误:{mutation.error.message}</div>} {mutation.isSuccess && <div>创建成功!</div>} </form> ); }
|
十、性能优化
10.1 React.memo - 组件记忆化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { memo } from 'react';
const ExpensiveComponent = memo(({ data }) => { console.log('ExpensiveComponent 渲染'); return <div>{data}</div>; });
const CustomMemo = memo( ({ user }) => { return <div>{user.name}</div>; }, (prevProps, nextProps) => { return prevProps.user.id === nextProps.user.id; } );
|
10.2 懒加载组件
1 2 3 4 5 6 7 8 9 10 11 12
| import { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() { return ( <Suspense fallback={<div>加载中...</div>}> <LazyComponent /> </Suspense> ); }
|
10.3 虚拟滚动(大列表优化)
1
| npm install react-window
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { FixedSizeList } from 'react-window';
function VirtualList({ items }) { const Row = ({ index, style }) => ( <div style={style}> {items[index].name} </div> ); return ( <FixedSizeList height={600} itemCount={items.length} itemSize={50} width="100%" > {Row} </FixedSizeList> ); }
|
10.4 性能优化清单
- ✅ 使用 React.memo 避免不必要的渲染
- ✅ 使用 useMemo 缓存计算结果
- ✅ 使用 useCallback 缓存函数
- ✅ 列表渲染使用稳定的 key
- ✅ 懒加载组件(React.lazy)
- ✅ 虚拟滚动处理大列表
- ✅ 避免在渲染中创建新对象/数组
- ✅ 使用 Web Workers 处理复杂计算
- ✅ 图片懒加载
- ✅ 代码分割(Code Splitting)
十一、React 18 新特性
11.1 并发渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import { startTransition } from 'react';
function SearchResults() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); const handleChange = (e) => { const value = e.target.value; setQuery(value); startTransition(() => { const filtered = largeDataSet.filter(item => item.includes(value) ); setResults(filtered); }); }; return ( <div> {/* 输入框始终保持流畅,不会因为搜索而卡顿 */} <input value={query} onChange={handleChange} /> <ul> {results.map(item => ( <li key={item}>{item}</li> ))} </ul> </div> ); }
|
11.2 useTransition
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { useTransition } from 'react';
function TabContainer() { const [isPending, startTransition] = useTransition(); const [tab, setTab] = useState('home'); const selectTab = (nextTab) => { startTransition(() => { setTab(nextTab); }); }; return ( <div> <button onClick={() => selectTab('home')}>首页</button> <button onClick={() => selectTab('profile')}>个人</button> {/* 显示加载状态:当 tab 内容正在渲染时显示 */} {isPending && <div>切换中...</div>} {/* 根据当前 tab 显示不同内容 */} <TabContent tab={tab} /> </div> ); }
|
11.3 useDeferredValue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import { useDeferredValue, useMemo } from 'react';
function SearchResults({ query }) { const deferredQuery = useDeferredValue(query); const results = useMemo(() => { console.log('执行搜索...', deferredQuery); return largeDataSet.filter(item => item.includes(deferredQuery) ); }, [deferredQuery]); return ( <ul> {results.map(item => ( <li key={item}>{item}</li> ))} </ul> ); }
function App() { const [query, setQuery] = useState(''); return ( <div> {/* 用户输入时,query 立即更新,输入框保持流畅 */} <input value={query} onChange={(e) => setQuery(e.target.value)} /> {/* SearchResults 使用延迟的 query,避免频繁搜索 */} <SearchResults query={query} /> </div> ); }
|
11.4 Suspense 数据获取
1 2 3 4 5 6 7 8 9
| import { Suspense } from 'react';
function App() { return ( <Suspense fallback={<div>加载中...</div>}> <UserProfile /> </Suspense> ); }
|
十二、高级特性
12.1 错误边界
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import React from 'react';
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { console.error('错误:', error, errorInfo); } render() { if (this.state.hasError) { return ( <div> <h1>出错了!</h1> <p>{this.state.error?.message}</p> </div> ); } return this.props.children; } }
<ErrorBoundary> <MyComponent /> </ErrorBoundary>
|
12.2 Portals - 传送门
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { createPortal } from 'react-dom';
function Modal({ children, isOpen }) { if (!isOpen) return null; return createPortal( <div className="modal-overlay"> <div className="modal-content"> {children} </div> </div>, document.getElementById('modal-root') ); }
|
十三、项目结构建议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| src/ ├── components/ # 通用组件 │ ├── Button/ │ │ ├── index.tsx │ │ └── Button.module.css │ ├── Input/ │ └── Modal/ ├── pages/ # 页面组件 │ ├── Home/ │ ├── About/ │ └── User/ ├── hooks/ # 自定义 Hooks │ ├── useAuth.ts │ ├── useFetch.ts │ └── useLocalStorage.ts ├── store/ # Redux 状态管理 │ ├── index.ts │ └── slices/ │ ├── counterSlice.ts │ └── userSlice.ts ├── api/ # API 请求 │ ├── request.ts │ ├── user.ts │ └── post.ts ├── utils/ # 工具函数 │ ├── format.ts │ └── validate.ts ├── constants/ # 常量 │ └── index.ts ├── types/ # TypeScript 类型 │ └── index.ts ├── App.tsx └── index.tsx
|
十四、常见错误与解决
14.1 直接修改 state
1 2 3 4 5 6 7
| state.value = 1; list.push(item);
setState({ ...state, value: 1 }); setList([...list, item]);
|
14.2 useEffect 依赖项缺失
1 2 3 4 5 6 7 8 9
| useEffect(() => { fetchData(userId); }, []);
useEffect(() => { fetchData(userId); }, [userId]);
|
14.3 key 使用 index
1 2 3 4 5
| {list.map((item, index) => <li key={index}>{item}</li>)}
{list.map(item => <li key={item.id}>{item}</li>)}
|
14.4 在循环/条件中使用 Hook
1 2 3 4 5 6 7 8 9 10
| if (condition) { const [state, setState] = useState(0); }
const [state, setState] = useState(0); if (condition) { }
|
总结
这份笔记涵盖了:
基础部分:
Hooks 核心:
- useState、useEffect、useContext、useReducer
- useRef、useMemo、useCallback、自定义 Hook
进阶内容:
React 全家桶:
- React Router(路由管理)
- Redux Toolkit(状态管理)
- Axios(HTTP 请求)
- React Query(数据获取)
性能优化:
React 18 新特性:
- 并发渲染、useTransition、useDeferredValue、Suspense
最佳实践:
学习建议:
- 先掌握基础语法和核心 Hooks
- 理解组件通信模式
- 学习 TypeScript 集成
- 实践 React Router 和状态管理
- 深入性能优化和高级特性
- 通过实际项目巩固知识
推荐学习路径:
基础入门(1-2周)→ Hooks 深入(1周)→ 全家桶实践(2-3周)→ 性能优化(1周)→ 项目实战(持续)