⚛️ React 技术学习指南

从入门到核心概念的全面掌握

📖 引言:认识 React

React 是由 Facebook 开发的用于构建用户界面的 JavaScript 库。它通过组件化声明式编程的设计思想,彻底改变了前端开发的方式。

React 解决的核心问题:

核心设计思想:

🧩 组件与 JSX

React 组件是构建应用的基本单元。JSX 是 JavaScript 的语法扩展,允许在 JS 中编写类似 HTML 的代码。

JSX 核心特性:

  • JSX 不是 HTML,而是 JavaScript 语法糖,最终会被编译为 React.createElement() 调用
  • 使用 className 而非 class,使用 htmlFor 而非 for
  • 嵌入 JavaScript 表达式使用 {} 花括号
  • 所有标签必须闭合,如 <img /><input />

函数组件示例

function WelcomeMessage({ name }) { return <h1>欢迎, {name}!</h1>; } // 使用组件 <WelcomeMessage name="开发者" />

组件工作原理

Props name: "开发者" age: 25 Component 处理逻辑 JSX/DOM <h1>欢迎, 开发者!</h1> State (内部状态) count: 0

🔄 状态与生命周期

State (状态) 是组件内部的数据,当状态改变时,组件会重新渲染。useState 是最常用的 Hook。

计数器示例

function Counter() { const [count, setCount] = useState(0); return ( <div> <p>当前计数: {count}</p> <button onClick={() => setCount(count + 1)}> 增加 </button> </div> ); }

状态更新流程

用户交互 点击按钮 触发 状态更新 setCount(n+1) 触发 组件重渲染 Re-render 更新完成 UI 更新 显示新的 count 值

📡 Props 与数据流

React 采用单向数据流:数据从父组件通过 Props 传递给子组件,保证数据流向清晰可控。

父子组件通信示例

// 父组件 function UserList() { const users = [ { id: 1, name: '张三' }, { id: 2, name: '李四' } ]; return users.map(user => <UserItem key={user.id} user={user} /> ); } // 子组件 function UserItem({ user }) { return <div>{user.name}</div>; }

数据流向图

父组件 UserList users = [{...}, {...}] props ↓ props ↓ 子组件 UserItem user.id: 1 user.name: "张三" 子组件 UserItem user.id: 2 user.name: "李四" ✓ 单向数据流:父 → 子

🪝 Hooks 深入理解

Hooks 让函数组件拥有状态和生命周期能力。useEffect 用于处理副作用(如数据获取、订阅等)。

useEffect 示例

function UserProfile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { // 组件挂载时获取数据 fetch(`/api/users/${userId}`) .then(res => res.json()) .then(data => setUser(data)); // 清理函数(组件卸载时执行) return () => { console.log('清理资源'); }; }, [userId]); // 依赖项: userId 变化时重新执行 return <div>{user?.name}</div>; }

useEffect 依赖项最佳实践:

  • [] 空数组:仅在组件挂载时执行一次
  • [dep] 指定依赖:dep 变化时重新执行
  • 无依赖项:每次渲染后都执行(慎用)
  • 必须包含 effect 中使用的所有外部变量

useEffect 执行流程

组件渲染 首次/重新渲染 检查依赖项 [userId] 是否发生变化? 执行清理函数 return () => {...} (如果存在) 跳过执行 不执行副作用 执行副作用函数

🌳 虚拟 DOM 与协调

React 通过虚拟 DOM (Virtual DOM) 优化性能。当状态改变时,React 先在内存中对比新旧虚拟 DOM 树,计算出最小差异,然后只更新必要的真实 DOM 节点。

虚拟 DOM 对比过程

旧虚拟 DOM <div id="app"> <h1>旧</h1> <p>内容</p> 新虚拟 DOM <div id="app"> <h1>新</h1> <p>内容</p> Diff 算法对比 ⚡ 差异计算结果 ✗ <h1> 文本变化: "旧" → "新" ✓ <p> 无变化,复用 只更新 <h1> 的文本节点

协调过程的关键优化:

🔗 Context API

Context API 提供了一种在组件树中传递数据的方法,无需手动逐层传递 props。适用于主题、用户信息、语言设置等全局数据。

基本使用示例

// 创建 Context const ThemeContext = React.createContext('light'); // 提供者组件 function App() { const [theme, setTheme] = useState('dark'); return ( <ThemeContext.Provider value={theme}> <Toolbar /> </ThemeContext.Provider> ); } // 消费者组件 function ThemedButton() { const theme = useContext(ThemeContext); return <button className={theme}>按钮</button>; }

Context 数据流

Provider (提供者) value={theme} 跨层级传递 消费者组件 A useContext(ThemeContext) 消费者组件 B useContext(ThemeContext) 消费者组件 C useContext(ThemeContext) ✓ 避免 props 逐层传递(Prop Drilling)

⚠️ 使用建议:

  • 仅用于真正全局的数据(主题、用户状态等)
  • Context 变化会导致所有消费者重新渲染,注意性能影响
  • 可以拆分多个 Context 以减少不必要的渲染
  • 对于复杂状态管理,考虑使用 Redux 等状态管理库

📦 Redux 状态管理

Redux 是一个用于 JavaScript 应用的可预测状态容器,特别适合大型应用的复杂状态管理。它基于三大核心原则:单一数据源、状态只读、使用纯函数修改状态。

核心概念

Redux 数据流

UI 组件 用户交互 dispatch Action {type: 'ADD'} Reducer 计算新状态 Store 更新状态 subscribe (订阅更新)

基本使用示例

// 1. 定义 Action Types const INCREMENT = 'INCREMENT'; const DECREMENT = 'DECREMENT'; // 2. 创建 Action Creators const increment = () => ({ type: INCREMENT }); const decrement = () => ({ type: DECREMENT }); // 3. 创建 Reducer const counterReducer = (state = { count: 0 }, action) => { switch (action.type) { case INCREMENT: return { count: state.count + 1 }; case DECREMENT: return { count: state.count - 1 }; default: return state; } }; // 4. 创建 Store import { createStore } from 'redux'; const store = createStore(counterReducer); // 5. 在组件中使用 (React-Redux) import { useSelector, useDispatch } from 'react-redux'; function Counter() { const count = useSelector(state => state.count); const dispatch = useDispatch(); return ( <div> <p>计数: {count}</p> <button onClick={() => dispatch(increment())}>+</button> <button onClick={() => dispatch(decrement())}>-</button> </div> ); }

Redux Toolkit (推荐)

Redux Toolkit 是 Redux 官方推荐的工具集,简化了 Redux 开发流程。

// 使用 createSlice 简化代码 import { createSlice, configureStore } 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; } } }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; const store = configureStore({ reducer: { counter: counterSlice.reducer } }); // 组件中使用 function Counter() { const count = useSelector(state => state.counter.value); const dispatch = useDispatch(); return ( <div> <p>{count}</p> <button onClick={() => dispatch(increment())}>增加</button> <button onClick={() => dispatch(incrementByAmount(5))}>+5</button> </div> ); }

Redux vs Context API - 何时使用?

  • 使用 Redux:大型应用、复杂状态逻辑、需要时间旅行调试、状态频繁更新、多个组件共享状态
  • 使用 Context:小型应用、简单全局状态(主题、语言)、状态更新不频繁、避免 props 逐层传递
  • Redux Toolkit 大幅简化了 Redux 使用,推荐新项目直接使用
  • 避免将所有状态都放入 Redux,组件私有状态仍使用 useState
  • 可以结合使用:Redux 管理全局状态,Context 传递配置信息

异步操作 - Redux Thunk

// 使用 Redux Thunk 处理异步逻辑 import { createAsyncThunk } from '@reduxjs/toolkit'; // 创建异步 thunk const fetchUser = createAsyncThunk( 'users/fetchById', 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; }) .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; }); } }); // 使用 function UserProfile({ userId }) { const dispatch = useDispatch(); const { data, loading } = useSelector(state => state.user); useEffect(() => { dispatch(fetchUser(userId)); }, [userId]); if (loading) return <div>加载中...</div>; return <div>{data?.name}</div>; }

⚡ 性能优化

React 应用的性能优化是构建高质量应用的关键。以下是几种常用的优化技术。

1. React.memo - 防止不必要的重渲染

// 子组件只在 props 变化时重新渲染 const MemoizedChild = React.memo(function Child({ name, age }) { console.log('Child 渲染'); return <div>{name} - {age}岁</div>; }); // 自定义比较函数 const MemoizedChild = React.memo(Child, (prevProps, nextProps) => { return prevProps.id === nextProps.id; // 返回 true 则跳过渲染 });

2. useMemo - 缓存计算结果

function ProductList({ products, filter }) { // 只在 products 或 filter 变化时重新计算 const filteredProducts = useMemo(() => { console.log('执行过滤逻辑'); return products.filter(p => p.category === filter); }, [products, filter]); return <div>{filteredProducts.map(p => ...)}</div>; }

3. useCallback - 缓存函数引用

function Parent() { const [count, setCount] = useState(0); // 函数引用保持不变,避免子组件重渲染 const handleClick = useCallback(() => { console.log('点击了按钮'); }, []); // 依赖项为空,函数永不改变 return <MemoizedChild onClick={handleClick} />; }

4. 代码分割 (Code Splitting)

// 使用 React.lazy 和 Suspense 实现懒加载 const LazyComponent = React.lazy(() => import('./HeavyComponent')); function App() { return ( <Suspense fallback={<div>加载中...</div>}> <LazyComponent /> </Suspense> ); }

性能优化决策流程

发现性能问题 1. 使用 React DevTools Profiler 分析 2. 识别渲染瓶颈组件 使用 React.memo 使用 useMemo 使用 useCallback

🎨 自定义 Hooks

自定义 Hooks 允许你提取组件逻辑,实现代码复用。自定义 Hook 是一个以 "use" 开头的函数,内部可以调用其他 Hooks。

示例:useLocalStorage Hook

function useLocalStorage(key, initialValue) { // 从 localStorage 读取初始值 const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; } catch (error) { return initialValue; } }); // 更新 state 和 localStorage const setValue = (value) => { try { setStoredValue(value); window.localStorage.setItem(key, JSON.stringify(value)); } catch (error) { console.error(error); } }; return [storedValue, setValue]; } // 使用自定义 Hook function App() { const [name, setName] = useLocalStorage('name', 'Anonymous'); return <input value={name} onChange={e => setName(e.target.value)} />; }

示例:useFetch Hook

function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let isMounted = true; fetch(url) .then(res => res.json()) .then(data => { if (isMounted) { setData(data); setLoading(false); } }) .catch(err => { if (isMounted) { setError(err); setLoading(false); } }); return () => { isMounted = false; }; }, [url]); return { data, loading, error }; } // 使用 function UserProfile({ userId }) { const { data, loading, error } = useFetch(`/api/users/${userId}`); if (loading) return <div>加载中...</div>; if (error) return <div>错误: {error.message}</div>; return <div>{data?.name}</div>; }

自定义 Hooks 最佳实践:

  • 命名必须以 "use" 开头,遵循 Hooks 命名约定
  • 提取可复用的状态逻辑,而非 UI
  • 每个自定义 Hook 应该有单一职责
  • 返回值可以是数组(类似 useState)或对象(更灵活)
  • 在多个组件中使用时,每个组件都有独立的 state

✨ 最佳实践

1. 正确使用 key 属性

在渲染列表时,为每个元素提供唯一且稳定的 key,帮助 React 识别哪些元素发生了变化。

// ✓ 正确:使用唯一ID {users.map(user => <UserItem key={user.id} user={user} /> )} // ✗ 错误:使用索引(数据顺序变化时会出问题) {users.map((user, index) => <UserItem key={index} user={user} /> )}

2. 组件单一职责原则

每个组件应该只负责一个功能,保持组件小而专注,便于复用和测试。

// ✓ 好的设计:职责分离 function UserProfile({ user }) { return ( <> <UserAvatar url={user.avatar} /> <UserInfo name={user.name} email={user.email} /> </> ); }

3. 避免不必要的渲染

使用 React.memouseMemouseCallback 优化性能,但不要过度优化。

// 使用 React.memo 避免不必要的重渲染 const ExpensiveComponent = React.memo(({ data }) => { return <div>{/* 复杂渲染逻辑 */}</div>; }); // 使用 useMemo 缓存计算结果 const sortedData = useMemo(() => { return data.sort((a, b) => a.value - b.value); }, [data]);

4. 正确管理副作用

在 useEffect 中处理异步操作时,注意清理和竞态条件。

useEffect(() => { let cancelled = false; async function fetchData() { const result = await api.getData(); if (!cancelled) { setData(result); } } fetchData(); // 清理函数防止内存泄漏 return () => { cancelled = true; }; }, []);

5. 状态提升与组合优于继承

React 推崇组合而非继承。当多个组件需要共享状态时,将状态提升到最近的公共父组件。

// ✓ 使用组合 function Dialog({ title, children }) { return ( <div className="dialog"> <h2>{title}</h2> {children} </div> ); } // 使用 <Dialog title="欢迎"> <p>感谢访问!</p> <button>确定</button> </Dialog>

6. 避免在渲染中创建新对象/数组/函数

每次渲染创建新引用会导致子组件不必要的重新渲染。

// ✗ 错误:每次渲染都创建新对象 function Parent() { return <Child config={{ theme: 'dark' }} />; } // ✓ 正确:使用 useMemo 缓存 function Parent() { const config = useMemo(() => ({ theme: 'dark' }), []); return <Child config={config} />; }