React深度实践¶
📚 章节目标¶
本章节将深入讲解React框架的核心概念和高级特性,包括Hooks、Context、状态管理、性能优化等,帮助学习者掌握React框架的深度应用。
学习目标¶
- 深入理解React Hooks原理和最佳实践
- 掌握React Context API的高级应用
- 熟练使用React Query进行数据获取
- 掌握React性能优化技巧
- 了解React Server Components
🎣 React Hooks深度解析¶
1. Hooks基础原理¶
1.1 Hooks的设计动机¶
JavaScript
// 类组件的问题:相关逻辑分散在不同生命周期方法中
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 }; // 类组件中状态必须是对象
}
// 生命周期问题:componentDidMount和componentDidUpdate中存在重复逻辑
componentDidMount() {
document.title = `Count: ${this.state.count}`;
}
componentDidUpdate() {
document.title = `Count: ${this.state.count}`;
}
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Increment
</button>
</div>
);
}
}
// Hooks解决方案:相关逻辑集中在一起,代码更简洁
function Counter() {
const [count, setCount] = useState(0); // 声明式状态
// useEffect统一处理挂载和更新,消除重复代码
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]); // 仅count变化时执行
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
1.2 Hooks的实现原理¶
JavaScript
// 简化的Hooks实现原理(React内部使用链表结构,此处用数组模拟)
let hooks = []; // 存储所有Hook的状态值
let currentHookIndex = 0; // 当前正在执行的Hook索引
function useState(initialValue) {
const index = currentHookIndex; // 闭包捕获当前索引,确保setState更新正确的状态
if (hooks[index] === undefined) {
hooks[index] = initialValue; // 仅首次渲染时初始化
}
const setState = (newValue) => {
hooks[index] = newValue; // 通过闭包中的index定位到对应状态
render(); // 触发重新渲染
};
currentHookIndex++; // 索引递增,为下一个Hook预留位置
return [hooks[index], setState];
}
function useEffect(callback, deps) {
const index = currentHookIndex;
const prevDeps = hooks[index]; // 获取上一次渲染时的依赖数组
// 浅比较依赖:首次渲染(prevDeps为空)或任一依赖变化时执行回调
const hasChangedDeps = !prevDeps || !deps.every((dep, i) => dep === prevDeps[i]);
if (hasChangedDeps) {
callback();
hooks[index] = deps; // 保存本次依赖用于下次比较
}
currentHookIndex++;
}
function render() {
currentHookIndex = 0; // 每次渲染重置索引,保证Hooks调用顺序一致
// React 18+ 使用 createRoot API
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
}
2. 核心Hooks详解¶
2.1 useState¶
JavaScript
// 基础用法
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
// 函数式更新(当新状态依赖旧状态时,使用函数形式确保正确性)
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
// 使用函数式更新,每次都基于最新的pending状态计算
setCount(prev => prev + 1);
setCount(prev => prev + 1); // 连续调用两次,最终count+2
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment Twice</button>
</div>
);
}
// 惰性初始化(传入函数而非值,避免每次渲染都执行昂贵计算)
function UserProfile({ userId }) {
const [user, setUser] = useState(() => {
// 只在初始渲染时执行一次,后续渲染跳过此函数
return fetchUser(userId);
});
return <div>{user.name}</div>;
}
// 对象和数组状态(需要保持不可变性,每次更新返回新对象)
function Form() {
const [formData, setFormData] = useState({
username: '',
email: '',
age: 0,
});
const handleChange = (field, value) => {
// 展开运算符创建新对象,仅覆盖变化的字段(不可变更新模式)
setFormData(prev => ({
...prev,
[field]: value,
}));
};
return (
<form>
<input
value={formData.username}
onChange={(e) => handleChange('username', e.target.value)}
/>
<input
value={formData.email}
onChange={(e) => handleChange('email', e.target.value)}
/>
</form>
);
}
2.2 useEffect¶
JavaScript
// 基础用法
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // 依赖数组
if (!user) return <div>Loading...</div>;
return <div>{user.name}</div>;
}
// 清理函数(组件卸载或依赖变化时执行,防止内存泄漏)
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(roomId);
connection.connect();
// 返回清理函数:roomId变化时先断开旧连接,再建立新连接
return () => {
connection.disconnect();
};
}, [roomId]); // roomId变化时重新执行Effect
return <div>Connected to {roomId}</div>;
}
// 多个Effect
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]);
useEffect(() => {
if (user) {
fetchPosts(user.id).then(setPosts);
}
}, [user]);
return (
<div>
<h1>{user?.name}</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
// 性能优化:防抖搜索,避免每次按键都发送请求
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
if (query.trim() === '') {
setResults([]);
return; // 空查询直接返回,不设置定时器
}
// 延迟500ms执行搜索,实现防抖效果
const timer = setTimeout(() => {
search(query).then(setResults);
}, 500);
// 依赖变化时清除上一个定时器,只执行最后一次输入的搜索
return () => clearTimeout(timer);
}, [query]);
return (
<ul>
{results.map(result => (
<li key={result.id}>{result.title}</li>
))}
</ul>
);
}
2.3 useContext¶
JavaScript
// 创建Context
const ThemeContext = React.createContext('light');
const UserContext = React.createContext(null);
// 提供者
function App() {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState({ name: 'John' });
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserContext.Provider value={user}>
<Toolbar />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// 消费者
function Toolbar() {
const { theme, setTheme } = useContext(ThemeContext);
const user = useContext(UserContext);
return (
<div className={theme}>
<p>User: {user.name}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</div>
);
}
// 自定义Hook简化Context使用,并添加安全校验
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
// 未在Provider内使用时抛出明确错误,便于调试
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
function Button() {
const { theme } = useTheme();
return <button className={theme}>Click me</button>;
}
2.4 useReducer¶
JavaScript
// 基础用法
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
// 复杂状态管理(当状态逻辑复杂时,useReducer比useState更清晰)
const initialState = {
todos: [],
filter: 'all',
loading: false, // 异步加载状态
error: null, // 错误信息
};
// 纯函数reducer:接收当前状态和action,返回新状态
function reducer(state, action) {
switch (action.type) {
case 'FETCH_TODOS_START': // 开始请求:显示加载、清除错误
return { ...state, loading: true, error: null };
case 'FETCH_TODOS_SUCCESS': // 请求成功:保存数据、关闭加载
return { ...state, loading: false, todos: action.payload };
case 'FETCH_TODOS_ERROR': // 请求失败:保存错误、关闭加载
return { ...state, loading: false, error: action.payload };
case 'ADD_TODO':
return { ...state, todos: [...state.todos, action.payload] };
case 'REMOVE_TODO':
return {
...state,
todos: state.todos.filter(todo => todo.id !== action.payload),
};
case 'SET_FILTER':
return { ...state, filter: action.payload };
default:
return state;
}
}
function TodoApp() {
// useReducer返回当前状态和dispatch函数(类似Redux模式)
const [state, dispatch] = useReducer(reducer, initialState);
// 组件挂载时获取待办数据
useEffect(() => {
dispatch({ type: 'FETCH_TODOS_START' });
fetchTodos()
.then(todos => dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: todos }))
.catch(error => dispatch({ type: 'FETCH_TODOS_ERROR', payload: error }));
}, []); // 空依赖数组:仅在挂载时执行一次
const addTodo = (text) => {
const todo = { id: Date.now(), text, completed: false };
dispatch({ type: 'ADD_TODO', payload: todo });
};
if (state.loading) return <div>Loading...</div>;
if (state.error) return <div>Error: {state.error.message}</div>;
return (
<div>
<TodoForm onSubmit={addTodo} />
<TodoList todos={state.todos} filter={state.filter} />
</div>
);
}
2.5 useMemo¶
JavaScript
// 缓存计算结果
function ExpensiveComponent({ a, b }) {
const expensiveValue = useMemo(() => {
console.log('Computing expensive value...');
return a * b + Math.sqrt(a + b);
}, [a, b]);
return <div>Result: {expensiveValue}</div>;
}
// 优化列表渲染(仅当users或filter变化时才重新过滤)
function UserList({ users, filter }) {
const filteredUsers = useMemo(() => {
return users.filter(user =>
user.name.toLowerCase().includes(filter.toLowerCase())
);
}, [users, filter]); // 依赖数组:只有这两个值变化时才重新计算
return (
<ul>
{filteredUsers.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
// 缓存复杂对象(避免每次渲染都创建新对象引用,导致子组件不必要的重渲染)
function Chart({ data, config }) {
const chartOptions = useMemo(() => ({
type: config.type,
data: {
labels: data.map(d => d.label),
datasets: [{
data: data.map(d => d.value),
backgroundColor: config.colors,
}],
},
options: {
responsive: true,
plugins: {
legend: { position: 'top' },
},
},
}), [data, config]);
return <Chart options={chartOptions} />;
}
2.6 useCallback¶
JavaScript
// 缓存函数
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []); // 空依赖数组,函数只创建一次
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<ChildComponent onClick={handleClick} />
</div>
);
}
// 避免子组件不必要的渲染(useCallback + React.memo配合使用)
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// useCallback缓存函数引用,防止每次渲染创建新函数导致子组件重渲染
const handleChange = useCallback((value) => {
setText(value);
}, []); // 空依赖:函数永远保持同一引用
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
{/* count变化时,ChildComponent不会重渲染,因为props未变 */}
<ChildComponent value={text} onChange={handleChange} />
</div>
);
}
// React.memo:浅比较props,仅props变化时才重新渲染
const ChildComponent = React.memo(function ChildComponent({ value, onChange }) {
console.log('ChildComponent rendered');
return <input value={value} onChange={(e) => onChange(e.target.value)} />;
});
2.7 useRef¶
JavaScript
// 访问DOM元素
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus</button>
</div>
);
}
// 保存可变值(useRef在组件整个生命周期内保持同一引用)
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null); // 用ref保存定时器ID,修改时不触发重渲染
useEffect(() => {
intervalRef.current = setInterval(() => {
setCount(c => c + 1); // 使用函数式更新,避免闭包引用过期的count
}, 1000);
return () => {
clearInterval(intervalRef.current); // 组件卸载时清除定时器
};
}, []);
return <div>Count: {count}</div>;
}
// 保存上一次的值(利用useEffect在渲染后执行的特性)
function PreviousValue({ value }) {
const prevRef = useRef(); // 初始值为undefined
useEffect(() => {
// Effect在渲染完成后执行,此时DOM已展示当前值
prevRef.current = value; // 保存当前值,下次渲染时作为"上一次的值"
}, [value]);
return (
<div>
<p>Current: {value}</p>
<p>Previous: {prevRef.current}</p>
</div>
);
}
2.8 useLayoutEffect¶
JavaScript
// 同步执行,在DOM更新后、浏览器绘制前执行(适合DOM测量)
function MeasureExample() {
const [height, setHeight] = useState(0);
// 回调ref:当DOM节点挂载/卸载时调用
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height); // 获取元素实际高度
}
}, []);
return (
<>
<h1 ref={measuredRef}>Hello, world!</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}
// 避免闪烁(useLayoutEffect同步执行,用户看不到中间状态)
function ScrollToBottom({ messages }) {
const containerRef = useRef(null);
// useLayoutEffect保证滚动在浏览器绘制前完成,避免闪烁
useLayoutEffect(() => {
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight;
}
}, [messages]); // 消息列表更新时自动滚动到底部
return (
<div ref={containerRef} style={{ overflow: 'auto', height: '300px' }}>
{messages.map(msg => (
<div key={msg.id}>{msg.text}</div>
))}
</div>
);
}
3. 自定义Hooks¶
3.1 数据获取Hook¶
JavaScript
// useFetch - 通用数据获取Hook(封装loading/error/data三种状态)
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// 用ref保存options,避免options对象引用变化导致Effect重新执行
const optionsRef = useRef(options);
optionsRef.current = options;
useEffect(() => {
// AbortController用于在组件卸载或url变化时取消未完成的请求
const controller = new AbortController();
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, {
...optionsRef.current,
signal: controller.signal, // 关联取消信号
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const json = await response.json();
setData(json);
} catch (err) {
// 忽略因组件卸载导致的AbortError
if (err.name !== 'AbortError') {
setError(err);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => {
controller.abort(); // 清理:取消进行中的请求
};
}, [url]); // 仅url变化时重新请求
return { data, loading, error };
}
// 使用示例
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return null;
return <div>{user.name}</div>;
}
3.2 本地存储Hook¶
JavaScript
// useLocalStorage - 将状态持久化到localStorage
function useLocalStorage(key, initialValue) {
// 惰性初始化:首次渲染时从localStorage读取
const [storedValue, setStoredValue] = useState(() => {
try { // try/catch捕获异常
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue; // 已存在则解析,否则用默认值
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
// 支持函数式更新(与useState行为一致)
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore)); // 同步写入localStorage
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue]; // API与useState完全一致
}
// 使用示例
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Switch to {theme === 'light' ? 'dark' : 'light'} mode
</button>
);
}
3.3 防抖Hook¶
JavaScript
// useDebounce - 防抖Hook(延迟更新值,适用于搜索输入等场景)
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
// 值变化后延迟delay毫秒再更新
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// 值再次变化时清除上一个定时器,实现防抖
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue; // 返回防抖后的值
}
// 使用示例
function SearchInput() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
// 执行搜索
search(debouncedSearchTerm);
}
}, [debouncedSearchTerm]);
return (
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Search..."
/>
);
}
3.4 窗口大小Hook¶
JavaScript
// useWindowSize - 响应式监听窗口尺寸变化
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener('resize', handleResize); // 监听窗口resize事件
return () => window.removeEventListener('resize', handleResize); // 卸载时移除监听
}, []); // 空依赖:仅挂载/卸载时执行
return size;
}
// 使用示例
function ResponsiveComponent() {
const { width } = useWindowSize();
return (
<div>
{width < 768 ? <MobileView /> : <DesktopView />}
</div>
);
}
3.5 表单Hook¶
JavaScript
// useForm - 通用表单管理Hook(管理值、校验、触摸状态)
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues); // 表单字段值
const [errors, setErrors] = useState({}); // 校验错误信息
const [touched, setTouched] = useState({}); // 字段是否被触摸过
const handleChange = (e) => {
const { name, value } = e.target;
setValues(prev => ({ ...prev, [name]: value }));
// 仅对已触摸过的字段实时校验(避免用户还未输入就显示错误)
if (touched[name] && validate) {
setErrors(prev => ({
...prev,
[name]: validate(values)[name],
}));
}
};
// 失焦时标记字段为已触摸,并触发校验
const handleBlur = (e) => {
const { name } = e.target;
setTouched(prev => ({ ...prev, [name]: true }));
if (validate) {
setErrors(prev => ({
...prev,
[name]: validate(values)[name],
}));
}
};
// 高阶函数:返回表单提交处理器
const handleSubmit = (callback) => (e) => {
e.preventDefault();
// 提交时校验所有字段
const validationErrors = validate ? validate(values) : {};
setErrors(validationErrors);
// 标记所有字段为已触摸(显示所有错误)
setTouched(
Object.keys(values).reduce((acc, key) => ({ ...acc, [key]: true }), {})
);
// 无错误时调用回调
if (Object.keys(validationErrors).length === 0) {
callback(values);
}
};
return {
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
};
}
// 使用示例
function LoginForm() {
const { values, errors, handleChange, handleBlur, handleSubmit } = useForm(
{ username: '', password: '' },
(values) => {
const errors = {};
if (!values.username) errors.username = 'Username is required';
if (!values.password) errors.password = 'Password is required';
return errors;
}
);
const onSubmit = (values) => {
console.log('Form submitted:', values);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
name="username"
value={values.username}
onChange={handleChange}
onBlur={handleBlur}
placeholder="Username"
/>
{errors.username && <span>{errors.username}</span>}
<input
name="password"
type="password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
placeholder="Password"
/>
{errors.password && <span>{errors.password}</span>}
<button type="submit">Submit</button>
</form>
);
}
🌐 React Context API高级应用¶
1. Context最佳实践¶
1.1 分离Context¶
JavaScript
// 不好的做法:所有状态放在一个Context(任一状态变化会导致所有消费者重渲染)
const AppContext = React.createContext();
function AppProvider({ children }) {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState(null);
const [language, setLanguage] = useState('en');
// 问题:value对象每次渲染都是新引用,且包含所有状态
return (
<AppContext.Provider value={{ theme, setTheme, user, setUser, language, setLanguage }}>
{children}
</AppContext.Provider>
);
}
// 好的做法:按职责分离Context,各自独立更新
const ThemeContext = React.createContext();
const UserContext = React.createContext();
const LanguageContext = React.createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
1.2 优化Context性能¶
JavaScript
// 不好的做法:整个Context变化导致所有消费者重新渲染
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const [colors, setColors] = useState({
primary: '#007bff',
secondary: '#6c757d',
});
return (
<ThemeContext.Provider value={{ theme, setTheme, colors, setColors }}>
{children}
</ThemeContext.Provider>
);
}
// 好的做法:将状态值和修改函数分到不同Context,减少不必要的重渲染
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const [colors, setColors] = useState({
primary: '#007bff',
secondary: '#6c757d',
});
return (
// 只读数据和修改方法分离:读取theme的组件不会因setTheme引用变化而重渲染
<ThemeContext.Provider value={{ theme, colors }}>
<ThemeDispatchContext.Provider value={{ setTheme, setColors }}>
{children}
</ThemeDispatchContext.Provider>
</ThemeContext.Provider>
);
}
// 消费者Hook:按需订阅,只读组件用useTheme,修改组件用useThemeDispatch
function useTheme() {
return useContext(ThemeContext);
}
function useThemeDispatch() {
return useContext(ThemeDispatchContext);
}
2. 复杂场景应用¶
2.1 多层Context嵌套¶
JavaScript
function App() {
return (
<ThemeProvider>
<AuthProvider>
<LanguageProvider>
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Router>
</LanguageProvider>
</AuthProvider>
</ThemeProvider>
);
}
function Dashboard() {
const { theme } = useTheme();
const { user } = useAuth();
const { language } = useLanguage();
return (
<div className={theme}>
<h1>Welcome, {user.name}</h1>
<p>Language: {language}</p>
</div>
);
}
2.2 Context组合¶
JavaScript
// 创建组合Hook
function useApp() {
const theme = useTheme();
const auth = useAuth();
const language = useLanguage();
return {
theme,
auth,
language,
};
}
// 使用
function MyComponent() {
const { theme, auth, language } = useApp();
return (
<div>
<p>Theme: {theme}</p>
<p>User: {auth.user?.name}</p>
<p>Language: {language}</p>
</div>
);
}
📊 React Query数据获取¶
1. 基础使用¶
JavaScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// 获取数据(useQuery自动管理缓存、重试、后台刷新)
function UserProfile({ userId }) {
const { data, isLoading, error } = useQuery({
queryKey: ['user', userId], // 查询键:用于缓存标识和自动重获取
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{data.name}</div>;
}
// 更新数据(useMutation处理写操作,配合缓存失效实现数据同步)
function UpdateUser({ userId }) {
const queryClient = useQueryClient(); // 获取查询客户端实例
const mutation = useMutation({
mutationFn: (userData) =>
fetch(`/api/users/${userId}`, {
method: 'PUT',
body: JSON.stringify(userData),
}).then(res => res.json()),
onSuccess: () => {
// 更新成功后使相关缓存失效,触发自动重新获取
queryClient.invalidateQueries({ queryKey: ['user', userId] });
},
});
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
mutation.mutate(Object.fromEntries(formData));
};
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" />
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Saving...' : 'Save'}
</button>
</form>
);
}
2. 高级特性¶
2.1 缓存和重新验证¶
JavaScript
// 自动重新验证
function UserProfile({ userId }) {
const { data } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
staleTime: 5 * 60 * 1000, // 5分钟内不重新获取
gcTime: 10 * 60 * 1000, // 10分钟后清除缓存(TanStack Query v5 已将 cacheTime 重命名为 gcTime)
refetchOnWindowFocus: true, // 窗口聚焦时重新获取
refetchOnReconnect: true, // 网络重连时重新获取
});
return <div>{data?.name}</div>;
}
// 手动重新验证
function RefreshButton() {
const queryClient = useQueryClient();
const handleClick = () => {
queryClient.invalidateQueries({ queryKey: ['user'] });
};
return <button onClick={handleClick}>Refresh</button>;
}
2.2 分页和无限滚动¶
JavaScript
// 分页
function UserList() {
const [page, setPage] = useState(1);
const { data, isLoading } = useQuery({
queryKey: ['users', page],
queryFn: () => fetch(`/api/users?page=${page}`).then(res => res.json()),
});
return (
<div>
{isLoading ? (
<div>Loading...</div>
) : (
<>
<ul>
{data.users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
<button
onClick={() => setPage(p => p - 1)}
disabled={page === 1}
>
Previous
</button>
<button
onClick={() => setPage(p => p + 1)}
disabled={!data.hasMore}
>
Next
</button>
</>
)}
</div>
);
}
// 无限滚动(结合useInfiniteQuery和IntersectionObserver实现)
function InfiniteUserList() {
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } =
useInfiniteQuery({
queryKey: ['users'],
queryFn: ({ pageParam }) =>
fetch(`/api/users?page=${pageParam}`).then(res => res.json()),
initialPageParam: 1, // TanStack Query v5 必须显式指定初始页码
// 根据上一页响应决定下一页参数,返回undefined表示没有更多数据
getNextPageParam: (lastPage) => lastPage.hasMore ? lastPage.nextPage : undefined,
});
const observerRef = useRef(); // 目标元素ref,用于触发加载更多
useEffect(() => {
// IntersectionObserver:当目标元素进入视口时触发回调
const observer = new IntersectionObserver(
(entries) => {
// 元素可见 && 还有下一页 && 当前未在加载中
if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
},
{ threshold: 1 } // 完全可见时触发
);
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => {
if (observerRef.current) {
observer.unobserve(observerRef.current);
}
};
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
return (
<div>
{data?.pages.map((page) => (
<ul key={page.page}>
{page.users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
))}
<div ref={observerRef} />
{isFetchingNextPage && <div>Loading more...</div>}
</div>
);
}
2.3 乐观更新¶
JavaScript
function TodoList() {
const queryClient = useQueryClient();
const addTodoMutation = useMutation({
mutationFn: (text) =>
fetch('/api/todos', {
method: 'POST',
body: JSON.stringify({ text }),
}).then(res => res.json()),
// onMutate在请求发出前执行,用于乐观更新
onMutate: async (newTodo) => {
// 取消正在进行的查询,避免覆盖乐观更新的数据
await queryClient.cancelQueries({ queryKey: ['todos'] });
// 保存之前的数据(用于回滚)
const previousTodos = queryClient.getQueryData(['todos']);
// 乐观更新:立即在UI上显示新数据,无需等待服务器响应
queryClient.setQueryData(['todos'], (old) => [
...old, // ...展开运算符:展开数组/对象
{ id: Date.now(), text: newTodo, completed: false },
]);
return { previousTodos }; // 传递给onError用于回滚
},
onError: (err, newTodo, context) => {
// 请求失败时回滚到之前的数据
queryClient.setQueryData(['todos'], context.previousTodos);
},
onSettled: () => {
// 无论成功或失败,都重新获取以确保数据一致性
queryClient.invalidateQueries({ queryKey: ['todos'] });
},
});
const handleSubmit = (e) => {
e.preventDefault();
const text = e.target.elements.text.value;
addTodoMutation.mutate(text);
e.target.reset();
};
return (
<div>
<form onSubmit={handleSubmit}>
<input name="text" placeholder="Add todo" />
<button type="submit" disabled={addTodoMutation.isPending}>
Add
</button>
</form>
</div>
);
}
⚡ React性能优化¶
1. 组件优化¶
1.1 React.memo¶
JavaScript
// 避免不必要的渲染
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
console.log('ExpensiveComponent rendered');
return <div>{data.map(item => <div key={item.id}>{item.name}</div>)}</div>;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [data, setData] = useState([]);
useEffect(() => {
fetchData().then(setData);
}, []);
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<ExpensiveComponent data={data} />
</div>
);
}
// 自定义比较函数(默认浅比较不够用时,手动控制重渲染条件)
const MemoizedComponent = React.memo(
function MyComponent({ user, onClick }) {
return <div onClick={() => onClick(user.id)}>{user.name}</div>;
},
(prevProps, nextProps) => {
// 返回true表示props相同(不重渲染),false表示需要重渲染
return prevProps.user.id === nextProps.user.id;
}
);
1.2 useMemo和useCallback¶
JavaScript
function ParentComponent() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([]);
// 缓存计算结果
const expensiveValue = useMemo(() => {
console.log('Computing expensive value...');
return items.reduce((sum, item) => sum + item.value, 0);
}, [items]);
// 缓存函数
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return (
<div>
<p>Count: {count}</p>
<p>Total: {expensiveValue}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<ChildComponent onClick={handleClick} />
</div>
);
}
2. 列表优化¶
2.1 虚拟列表¶
JavaScript
import { FixedSizeList } from 'react-window'; // 虚拟列表库,仅渲染可见区域的DOM
function VirtualizedList({ items }) {
// 行渲染组件:style由react-window计算,包含定位信息
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={400} // 容器高度
itemCount={items.length} // 总数据量
itemSize={35} // 每行固定高度(px)
width="100%"
>
{Row}
</FixedSizeList>
);
}
2.2 key优化¶
JavaScript
// 不好的做法:使用index作为key
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo, index) => (
<li key={index}>{todo.text}</li>
))}
</ul>
);
}
// 好的做法:使用唯一ID作为key
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
3. 代码分割¶
3.1 React.lazy¶
JavaScript
import { lazy, Suspense } from 'react';
// lazy()实现动态导入,每个页面组件会被打包为独立chunk
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
const Contact = lazy(() => import('./Contact'));
function App() {
return (
// Suspense包裹懒加载组件,在加载期间显示fallback内容
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
);
}
3.2 路由级代码分割¶
JavaScript
import { lazy, Suspense } from 'react';
// 路由配置数组:每个路由对应一个懒加载组件,访问时才按需下载
const routes = [
{
path: '/',
component: lazy(() => import('./pages/Home')),
},
{
path: '/dashboard',
component: lazy(() => import('./pages/Dashboard')),
},
{
path: '/settings',
component: lazy(() => import('./pages/Settings')),
},
];
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
{routes.map(route => ( // map转换每个元素;filter筛选;reduce累积
<Route
key={route.path}
path={route.path}
element={<route.component />}
/>
))}
</Routes>
</Suspense>
);
}
🚀 React Server Components¶
1. 基础概念¶
JavaScript
// 服务器组件(默认):在服务器端执行,可直接访问数据库,不增加客户端JS体积
async function BlogPost({ id }) {
const post = await db.post.findUnique({ where: { id } }); // 服务端直接查询DB
return <article>{post.content}</article>;
}
// 客户端组件:需要'use client'指令声明,支持交互和浏览器API
'use client';
import { useState } from 'react';
function LikeButton() {
const [likes, setLikes] = useState(0); // 客户端状态,服务器组件中不可用
return <button onClick={() => setLikes(likes + 1)}>{likes}</button>;
}
2. 混合使用¶
JavaScript
// 服务器组件(可嵌入客户端组件作为子组件)
async function BlogPage({ params }) { // async定义异步函数;await等待Promise完成
const post = await db.post.findUnique({ where: { id: params.id } }); // await等待异步操作完成
return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
{/* 服务器组件中使用客户端组件,通过props传递序列化数据 */}
<LikeButton postId={post.id} />
</div>
);
}
// 客户端组件:处理用户交互逻辑
'use client';
import { useState } from 'react';
function LikeButton({ postId }) {
const [likes, setLikes] = useState(0);
// 客户端发起API请求(不能直接访问数据库)
const handleLike = async () => { // const不可重新赋值;let块级作用域变量
const response = await fetch(`/api/posts/${postId}/like`, {
method: 'POST',
});
const data = await response.json();
setLikes(data.likes);
};
return <button onClick={handleLike}>{likes} likes</button>;
}
📝 练习题¶
1. 基础题¶
题目1:实现一个useCounter Hook
JavaScript
function useCounter(initialValue = 0) {
// 实现计数器Hook
// 返回 { count, increment, decrement, reset }
}
// 使用示例
function Counter() {
const { count, increment, decrement, reset } = useCounter(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
2. 进阶题¶
题目2:实现一个useAsync Hook
JavaScript
function useAsync(asyncFunction, immediate = true) {
// 实现异步Hook
// 返回 { data, error, loading, run }
}
// 使用示例
function UserProfile({ userId }) {
const { data, error, loading } = useAsync( // 解构赋值:从对象/数组提取值
() => fetch(`/api/users/${userId}`).then(res => res.json()), // 箭头函数:简洁的函数语法
!!userId
);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{data?.name}</div>; // ?.可选链:对象为null/undefined时安全返回undefined
}
3. 面试题¶
题目3:解释React的渲染机制和优化策略
JavaScript
// 答案要点:
// 1. React的渲染流程:状态变化 → 虚拟DOM计算 → diff算法 → 真实DOM更新
// 2. 优化策略:
// - 使用React.memo避免不必要的渲染
// - 使用useMemo缓存计算结果
// - 使用useCallback缓存函数
// - 代码分割和懒加载
// - 虚拟列表优化长列表
// - 避免内联对象和函数
🎯 本章总结¶
本章节深入讲解了React框架的核心概念和高级特性,包括Hooks、Context、React Query、性能优化等。关键要点:
- Hooks原理:理解Hooks的设计动机和实现原理
- 自定义Hooks:掌握自定义Hooks的编写技巧
- Context API:掌握Context的高级应用和性能优化
- React Query:掌握数据获取和状态管理
- 性能优化:掌握React性能优化的各种技巧
下一步将深入学习Vue框架的深度实践。