React18 完整学习笔记

这是一份完整的 React18 学习笔记,涵盖从基础到进阶的所有核心知识点。

学习路线图

1
基础入门 → Hooks 深入 → 组件通信 → TypeScript → 全家桶 → 性能优化 → React18 新特性

一、React 基础入门

1.1 项目搭建

创建 React 项目:

1
2
3
4
5
6
7
# JavaScript 项目
npx create-react-app my-app
cd my-app
npm start

# TypeScript 项目
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
// src/index.js
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 表达式
  • classclassNameforhtmlFor
  • 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() {
// [状态值, 更新函数] = useState(初始值)
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
// ❌ 错误:直接修改 state
const [user, setUser] = useState({ name: 'Jack' });
user.name = 'Tom'; // 直接修改原对象,React 检测不到变化,不会触发重新渲染

// ✅ 正确:创建新对象
setUser({ ...user, name: 'Tom' }); // 使用展开运算符创建新对象,React 能检测到变化

// ❌ 错误:直接修改数组
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);

// 1. 每次渲染后都执行(没有依赖数组)
useEffect(() => {
console.log('组件渲染了');
// 这个 effect 会在每次组件渲染后执行
});

// 2. 仅在挂载时执行一次(空依赖数组 [])
// 相当于类组件的 componentDidMount
useEffect(() => {
console.log('组件挂载了');

// 清理函数:在组件卸载时执行
// 相当于类组件的 componentWillUnmount
return () => {
console.log('组件卸载了');
};
}, []); // 空数组表示不依赖任何值,只执行一次

// 3. 仅在 count 变化时执行
// 相当于类组件的 componentDidUpdate,但只监听特定值
useEffect(() => {
console.log('count 变化了:', count);
// 只有当 count 的值改变时,这个 effect 才会重新执行
}, [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(() => {
// 为什么要定义 async 函数?
// 因为 useEffect 的回调函数不能直接是 async 函数
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]); // 当 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(() => {
// 建立 WebSocket 连接
const socket = new WebSocket(`ws://localhost:3000/chat/${roomId}`);

// 监听消息
socket.onmessage = (event) => {
const newMessage = JSON.parse(event.data);
setMessages(prev => [...prev, newMessage]);
};

// 清理函数:组件卸载或 roomId 变化时执行
// 非常重要!如果不清理,会导致内存泄漏
return () => {
console.log('关闭连接');
socket.close(); // 关闭 WebSocket 连接
};
}, [roomId]); // 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(() => {
// 使用函数式更新,确保获取最新的 seconds 值
setSeconds(prev => prev + 1);
}, 1000);

// 清理函数:停止或组件卸载时清除定时器
// 如果不清理,定时器会一直运行,导致内存泄漏
return () => {
console.log('清除定时器');
clearInterval(timer);
};
}, [isRunning]); // 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 {
// 同时使用 query 和 category 进行搜索
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;
}

// 设置延迟,500ms 后才发送请求
const timer = setTimeout(() => {
console.log('发送搜索请求:', query);
// 这里发送请求
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(data => setResults(data));
}, 500);

// 清理函数:如果用户继续输入,取消上一次的定时器
// 这样只有用户停止输入 500ms 后才会发送请求
return () => {
clearTimeout(timer);
};
}, [query]); // 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(() => {
// 初始化时从 localStorage 读取
return localStorage.getItem('theme') || 'light';
});

useEffect(() => {
// 每次 theme 变化时,保存到 localStorage
localStorage.setItem('theme', theme);

// 同时更新 body 的 class
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
// ❌ 错误1:忘记添加依赖项
useEffect(() => {
fetchData(userId); // 使用了 userId
}, []); // 但依赖数组是空的,userId 变化时不会重新执行

// ✅ 正确
useEffect(() => {
fetchData(userId);
}, [userId]); // 添加 userId 到依赖数组

// ❌ 错误2:在 useEffect 中直接修改 state
useEffect(() => {
count = count + 1; // 错误!
}, []);

// ✅ 正确
useEffect(() => {
setCount(prev => prev + 1); // 使用 setState
}, []);

// ❌ 错误3:忘记清理副作用
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
// 忘记返回清理函数,定时器会一直运行
}, []);

// ✅ 正确
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(timer); // 清理定时器
}, []);

// ❌ 错误4:useEffect 回调直接使用 async
useEffect(async () => { // 错误!
const data = await fetchData();
}, []);

// ✅ 正确:在内部定义 async 函数
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>
);
}

// 首次渲染输出:
// 1. 组件渲染
// 3. useEffect 执行(空依赖)
// 5. useEffect 执行(count 依赖) 0
// 2. useEffect 执行(无依赖)

// 点击按钮后输出:
// 1. 组件渲染
// 6. 清理函数执行(count 变化前)
// 5. useEffect 执行(count 依赖) 1
// 2. useEffect 执行(无依赖)

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';

// 1. 创建 Context(创建一个数据通道)
const ThemeContext = createContext();
const UserContext = createContext();

// 2. 提供 Context(在顶层组件提供数据)
function App() {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState({ name: '张三' });

return (
// Provider 组件用于提供数据,value 是要传递的数据
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserContext.Provider value={{ user, setUser }}>
{/* 所有子组件都可以访问这些数据,无需通过 props 逐层传递 */}
<Header />
<Content />
</UserContext.Provider>
</ThemeContext.Provider>
);
}

// 3. 消费 Context(在任意深层子组件中使用数据)
function Header() {
// 使用 useContext 获取 Context 中的数据
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';

// 1. 定义 reducer 函数(纯函数,根据 action 返回新的 state)
// state: 当前状态
// action: 动作对象,包含 type(动作类型)和可选的 payload(数据)
function reducer(state, action) {
// 根据不同的 action.type 返回不同的新状态
switch (action.type) {
case 'increment':
// 返回新的状态对象(count + 1)
return { count: state.count + 1 };
case 'decrement':
// 返回新的状态对象(count - 1)
return { count: state.count - 1 };
case 'reset':
// 重置为初始值
return { count: 0 };
default:
// 未知的 action 类型,抛出错误
throw new Error('Unknown action');
}
}

// 2. 使用 useReducer
function Counter() {
// useReducer 返回 [当前状态, dispatch 函数]
// dispatch 函数用于触发状态更新
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 有两个主要用途:

  1. 获取 DOM 元素
  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
import { useRef, useEffect } from 'react';

function TextInput() {
// 创建一个 ref 对象,初始值为 null
const inputRef = useRef(null);
// 创建一个用于保存计数的 ref(不会触发重新渲染)
const countRef = useRef(0);

useEffect(() => {
// 通过 ref.current 访问 DOM 元素
// 自动聚焦到输入框
inputRef.current.focus();
}, []);

const handleClick = () => {
// 访问 DOM 元素的值
console.log(inputRef.current.value);

// 修改 ref 的值不会触发组件重新渲染
// 适合保存不需要触发渲染的数据(如定时器 ID、上一次的值等)
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);

// useMemo 会缓存计算结果
// 只有当依赖项 items 变化时,才会重新计算
// 如果 items 没变,直接返回上次缓存的结果
const total = useMemo(() => {
console.log('计算总价...'); // 只有在 items 变化时才会打印
// 遍历所有商品,累加价格
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]); // 依赖数组:只有 items 变化时才重新计算

// 注意:点击按钮改变 count 时,total 不会重新计算
// 因为 items 没有变化,useMemo 会直接返回缓存的值

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';

// 使用 memo 包裹子组件,只有 props 变化时才重新渲染
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('');

// 问题:如果不使用 useCallback,每次 Parent 渲染时都会创建新的 handleClick 函数
// 新函数 !== 旧函数,导致 Button 组件认为 props 变化了,从而重新渲染

// 解决:使用 useCallback 缓存函数引用
// 只有当依赖项 count 变化时,才会创建新函数
const handleClick = useCallback(() => {
console.log('点击了', count);
}, [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
// useLocalStorage - 持久化状态到本地存储
function useLocalStorage(key, initialValue) {
// 初始化状态:从 localStorage 读取,如果没有则使用初始值
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
// 如果 localStorage 中有值,解析 JSON;否则使用初始值
return stored ? JSON.parse(stored) : initialValue;
});

// 每当 value 变化时,同步到 localStorage
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);

// 返回值和更新函数,用法和 useState 一样
return [value, setValue];
}

// 使用自定义 Hook
function App() {
// 用法和 useState 完全一样,但数据会自动保存到 localStorage
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
// useFetch - 封装数据请求逻辑
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]); // url 变化时重新请求

// 返回三个状态,让组件可以根据不同状态显示不同内容
return { data, loading, error };
}

// 使用自定义 Hook
function UserList() {
// 调用自定义 Hook,获取数据、加载状态、错误信息
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 使用规则:

  1. 只在函数组件最外层调用 Hook
  2. 不要在循环、条件或嵌套函数中调用
  3. 只在 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;

// 更新状态:使用计算属性名 [name] 动态更新对应字段
setFormData({
...formData, // 保留其他字段
// 如果是 checkbox,使用 checked;否则使用 value
[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
// Props 类型
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
// DOM 引用
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() {
// 路径参数:/users/:id
const { id } = useParams();

// 查询参数:/users/123?tab=profile&page=2
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>
}
/>

七、Redux Toolkit - 状态管理

7.1 安装与配置

1
npm install @reduxjs/toolkit react-redux
1
2
3
4
5
6
7
8
9
10
11
// store/index.js
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
// App.js
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
// store/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1; // 可以直接修改(内部使用 Immer)
},
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
// store/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 异步 thunk
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
npm install axios
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';

// GET 请求
const getUsers = async () => {
try {
const response = await axios.get('/api/users');
console.log(response.data);
} catch (error) {
console.error(error);
}
};

// POST 请求
const createUser = async (userData) => {
try {
const response = await axios.post('/api/users', userData);
console.log(response.data);
} catch (error) {
console.error(error);
}
};

// PUT 请求
const updateUser = async (id, userData) => {
await axios.put(`/api/users/${id}`, userData);
};

// DELETE 请求
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
// api/request.js
import axios from 'axios';

const request = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
});

// 请求拦截器
request.interceptors.request.use(
(config) => {
// 添加 token
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
// App.js
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, // 5秒内数据不过期
cacheTime: 10000, // 缓存10秒
});

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) => {
// 返回 true 表示不重新渲染
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 性能优化清单

  1. ✅ 使用 React.memo 避免不必要的渲染
  2. ✅ 使用 useMemo 缓存计算结果
  3. ✅ 使用 useCallback 缓存函数
  4. ✅ 列表渲染使用稳定的 key
  5. ✅ 懒加载组件(React.lazy)
  6. ✅ 虚拟滚动处理大列表
  7. ✅ 避免在渲染中创建新对象/数组
  8. ✅ 使用 Web Workers 处理复杂计算
  9. ✅ 图片懒加载
  10. ✅ 代码分割(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 包裹,告诉 React 这个更新可以被中断
// 如果用户继续输入,React 会中断这次搜索,优先处理新的输入
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() {
// isPending: 是否正在过渡中(低优先级更新是否在进行)
// startTransition: 用于标记低优先级更新的函数
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('home');

const selectTab = (nextTab) => {
// 将切换 tab 标记为低优先级更新
// 如果有更紧急的更新(如用户输入),React 会优先处理
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 }) {
// 创建一个延迟版本的 query
// 当 query 快速变化时,deferredQuery 会"延迟"更新
// 这样可以避免每次输入都触发昂贵的搜索操作
const deferredQuery = useDeferredValue(query);

// 使用延迟的查询值进行搜索
// 只有当 deferredQuery 变化时才重新搜索
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);
}, []); // 缺少 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) {
// 使用 state
}

总结

这份笔记涵盖了:

基础部分:

  • JSX 语法、组件、Props、事件处理

Hooks 核心:

  • useState、useEffect、useContext、useReducer
  • useRef、useMemo、useCallback、自定义 Hook

进阶内容:

  • 组件通信、表单处理、TypeScript 集成

React 全家桶:

  • React Router(路由管理)
  • Redux Toolkit(状态管理)
  • Axios(HTTP 请求)
  • React Query(数据获取)

性能优化:

  • React.memo、懒加载、虚拟滚动

React 18 新特性:

  • 并发渲染、useTransition、useDeferredValue、Suspense

最佳实践:

  • 项目结构、常见错误、性能优化清单

学习建议:

  1. 先掌握基础语法和核心 Hooks
  2. 理解组件通信模式
  3. 学习 TypeScript 集成
  4. 实践 React Router 和状态管理
  5. 深入性能优化和高级特性
  6. 通过实际项目巩固知识

推荐学习路径:
基础入门(1-2周)→ Hooks 深入(1周)→ 全家桶实践(2-3周)→ 性能优化(1周)→ 项目实战(持续)