React
React 基础
核心特性、虚拟 DOM 与 JSX
包含的题目:
- React 的核心特性有哪些?
- React 与 Vue 的区别是什么?(高频考点)
- 什么是虚拟 DOM?(高频考点)
- 虚拟 DOM 的原理是什么?
- 什么是 JSX?
- JSX 如何转换为 JavaScript?
- 什么是组件化?
- 什么是单向数据流?
详细答案:
React 是一个用于构建用户界面的 JavaScript 库,具有以下核心特性:
-
声明式编程:React 采用声明式范式,让开发者通过描述 UI 应该是什么样子来构建应用,而不是直接操作 DOM。
-
组件化:React 应用由组件构成,每个组件封装了自己的逻辑和样式,可以独立开发、测试和复用。
-
虚拟 DOM(Virtual DOM):React 在内存中维护一个轻量级的虚拟 DOM 树,当状态改变时,React 会计算出新的虚拟 DOM 树,然后通过高效的 Diff 算法比较新旧树,只将实际变化的部分更新到真实 DOM 中,避免整棵 DOM 树重绘。
虚拟 DOM 工作原理:
- 初始渲染时,React 根据 JSX 创建虚拟 DOM 树
- 状态更新时,React 重新创建新的虚拟 DOM 树
- 通过 Diff 算法比较两棵树,找出最小变更集
- 将变更批量更新到真实 DOM
-
JSX(JavaScript XML):JSX 是 JavaScript 的语法扩展,允许在 JavaScript 中编写类似 HTML 的代码,提高代码可读性。
JSX 转换过程:
// JSX 代码 const element = <h1 className="title">Hello, World!</h1>; // 被 Babel 转换为 React.createElement 调用 const element = React.createElement( 'h1', { className: 'title' }, 'Hello, World!' ); -
单向数据流:React 中的数据从父组件流向子组件,通过 props 传递。子组件不能直接修改父组件的状态,只能通过回调函数通知父组件状态变更。
-
React 与 Vue 的主要区别:
| 方面 | React | Vue |
|---|---|---|
| 核心思想 | 函数式编程,不可变性 | 响应式系统,可变状态 |
| 模板语法 | JSX(JavaScript) | 模板语法(HTML-based) |
| 状态管理 | 需要搭配 Redux/MobX 等 | 内置响应式系统 |
| 学习曲线 | 较高,需要理解函数式概念 | 较低,API 更简洁 |
| 生态规模 | 庞大,社区活跃 | 完善,官方维护稳定 |
补充说明:
- 虚拟 DOM 虽然提高了性能,但在简单应用中可能增加开销
- JSX 不是必需的,但推荐使用它提高开发效率
- 单向数据流使得数据流更可预测,便于调试和维护
React 组件
函数组件、类组件与生命周期
包含的题目: 9. 什么是 React 组件? 10. 函数组件与类组件的区别(高频考点) 11. 如何创建 React 组件? 12. 组件的 props 的使用 13. props 的验证方法 14. 组件的 state 的使用 15. state 的更新方法 16. state 的不可变性原则 17. 组件的生命周期有哪些?(高频考点) 18. componentDidMount 钩子的使用 19. componentDidUpdate 钩子的使用 20. componentWillUnmount 钩子的使用 21. 父子组件生命周期执行顺序
详细答案:
-
React 组件是构建 UI 的基本单元,可以是函数或类。组件接收输入(props)并返回 React 元素。
-
函数组件 vs 类组件:
| 特性 | 函数组件 | 类组件 |
|---|---|---|
| 语法 | 简洁的函数 | ES6 class |
| 状态 | 使用 useState Hook | this.state |
| 生命周期 | 使用 useEffect Hook | 生命周期方法 |
| 性能 | 轻量,无实例 | 较重,有实例 |
| this 绑定 | 无 | 需要绑定事件处理函数 |
- props 使用与验证:
// 函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 使用 PropTypes 进行类型检查
import PropTypes from 'prop-types';
Welcome.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number
};
Welcome.defaultProps = {
name: 'Guest'
};
- state 管理与不可变性:
// 类组件中的 state
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
// 正确:创建新对象/数组
increment() {
this.setState(prevState => ({
count: prevState.count + 1
}));
}
// 错误:直接修改 state
// this.state.count = this.state.count + 1;
}
- 生命周期方法:
class Example extends React.Component {
componentDidMount() {
// 组件挂载后执行,适合发起网络请求、订阅事件等
console.log('组件已挂载');
}
componentDidUpdate(prevProps, prevState) {
// 组件更新后执行,适合根据props/state变化执行操作
if (this.props.userId !== prevProps.userId) {
this.fetchData(this.props.userId);
}
}
componentWillUnmount() {
// 组件卸载前执行,适合清理定时器、取消订阅等
console.log('组件即将卸载');
}
}
- 父子组件生命周期执行顺序:
- 父组件 constructor
- 父组件 render
- 子组件 constructor
- 子组件 render
- 子组件 componentDidMount
- 父组件 componentDidMount
补充说明:
- 推荐使用函数组件 + Hooks,代码更简洁
- state 不可变性原则有助于避免副作用,便于追踪变化
- 生命周期方法在类组件中有效,函数组件使用等效的 Hooks
React Hooks
useState、useEffect 与其他 Hooks
包含的题目: 22. 什么是 React Hooks?(高频考点) 23. Hooks 的优势是什么? 24. Hooks 的规则有哪些? 25. useState Hook 的使用(高频考点) 26. useEffect Hook 的使用(高频考点) 27. useEffect 的依赖数组的作用 28. useEffect 的清理函数的使用 29. useContext Hook 的使用 30. useReducer Hook 的使用 31. useCallback Hook 的使用(高频考点) 32. useMemo Hook 的使用(高频考点) 33. useRef Hook 的使用 34. useLayoutEffect Hook 的使用 35. 自定义 Hooks(高频考点)
详细答案:
-
React Hooks 是 React 16.8 引入的特性,允许在函数组件中使用状态和其他 React 特性。
-
Hooks 规则:
- 只在最顶层调用 Hooks,不在循环、条件或嵌套函数中调用
- 只在 React 函数组件或自定义 Hooks 中调用 Hooks
-
useState Hook:
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', age: 0 });
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setCount(prev => prev + 1)}>+1(函数式更新)</button>
</div>
);
}
- useEffect Hook:
import { useEffect } from 'react';
function Example() {
// 1. 无依赖数组:每次渲染后都执行
useEffect(() => {
console.log('每次渲染后执行');
});
// 2. 空依赖数组:只在组件挂载后执行一次
useEffect(() => {
console.log('组件挂载后执行一次');
const timer = setInterval(() => {
console.log('定时器执行');
}, 1000);
// 清理函数:组件卸载时执行
return () => {
clearInterval(timer);
console.log('组件卸载,清理定时器');
};
}, []);
// 3. 有依赖数组:依赖值变化时执行
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`count 变化为: ${count}`);
document.title = `计数: ${count}`;
}, [count]); // 只在 count 变化时执行
}
- useCallback 与 useMemo:
import { useCallback, useMemo } from 'react';
function App() {
const [count, setCount] = useState(0);
// useCallback:缓存函数,避免子组件不必要的重渲染
const handleClick = useCallback(() => {
console.log('点击事件', count);
}, [count]); // 依赖数组:count 变化时重新创建函数
// useMemo:缓存计算结果,避免重复计算
const expensiveResult = useMemo(() => {
console.log('计算 expensiveResult');
let result = 0;
for (let i = 0; i < count * 1000000; i++) {
result += i;
}
return result;
}, [count]); // 只在 count 变化时重新计算
return <Child onClick={handleClick} />;
}
- useRef Hook:
import { useRef, useEffect } from 'react';
function Example() {
const inputRef = useRef(null);
const prevCountRef = useRef();
const count = 0;
useEffect(() => {
// 访问 DOM 节点
inputRef.current.focus();
// 保存前一次渲染的值
prevCountRef.current = count;
});
const prevCount = prevCountRef.current; // 获取之前的值
return <input ref={inputRef} type="text" />;
}
- 自定义 Hooks:
// 自定义 Hook:获取窗口大小
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);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// 使用自定义 Hook
function MyComponent() {
const { width, height } = useWindowSize();
return <div>窗口大小: {width} x {height}</div>;
}
补充说明:
- Hooks 使得状态逻辑更易于复用和测试
- useEffect 替代了类组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount
- 合理使用 useCallback 和 useMemo 优化性能
React 事件处理与表单
包含的题目: 36. React 事件的特点是什么? 37. 什么是合成事件?(高频考点) 38. 如何绑定事件? 39. 如何阻止事件冒泡? 40. 如何阻止事件默认行为? 41. 什么是受控组件?(高频考点) 42. 什么是非受控组件? 43. 受控组件与非受控组件的区别 44. 表单验证的方法
详细答案:
-
React 事件特点:
- React 使用合成事件系统,将所有事件委托到 document
- 事件命名使用驼峰命名法(onClick,不是 onclick)
- JSX 中传递函数作为事件处理函数,而不是字符串
-
合成事件:React 实现了跨浏览器的 W3C 事件系统,提供一致的事件接口,自动处理事件冒泡和默认行为。
-
事件绑定与处理:
function Button() {
// 方式1:内联箭头函数
const handleClick = (event) => {
event.stopPropagation(); // 阻止事件冒泡
event.preventDefault(); // 阻止默认行为
console.log('按钮被点击', event);
};
// 方式2:bind 绑定
const handleClick2 = function(event) {
this.setState({ clicked: true });
}.bind(this);
// 方式3:类方法 + 构造函数中 bind
// constructor() {
// this.handleClick3 = this.handleClick3.bind(this);
// }
return (
<div>
<button onClick={handleClick}>点击我</button>
<button onClick={(e) => handleClick(e)}>内联函数(不推荐,每次渲染创建新函数)</button>
<button onClick={handleClick2}>bind 方式</button>
</div>
);
}
- 受控组件:表单数据由 React 组件 state 管理。
function ControlledForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [selectedOption, setSelectedOption] = useState('option1');
const handleSubmit = (event) => {
event.preventDefault();
console.log('提交的数据:', { name, email, selectedOption });
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="姓名"
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="邮箱"
/>
<select value={selectedOption} onChange={(e) => setSelectedOption(e.target.value)}>
<option value="option1">选项1</option>
<option value="option2">选项2</option>
</select>
<button type="submit">提交</button>
</form>
);
}
- 非受控组件:表单数据由 DOM 自身管理,通过 ref 获取值。
function UncontrolledForm() {
const nameRef = useRef(null);
const fileRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
console.log('姓名:', nameRef.current.value);
console.log('文件:', fileRef.current.files[0]);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={nameRef} defaultValue="默认值" />
<input type="file" ref={fileRef} />
<button type="submit">提交</button>
</form>
);
}
- 受控 vs 非受控组件:
| 方面 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据管理 | React state 管理 | DOM 自身管理 |
| 即时验证 | 容易实现 | 较难实现 |
| 表单提交 | 通过 state 获取数据 | 通过 ref 获取数据 |
| 推荐场景 | 需要即时验证、动态表单 | 简单表单、文件上传 |
- 表单验证:
function FormWithValidation() {
const [formData, setFormData] = useState({ email: '', password: '' });
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!formData.email) newErrors.email = '邮箱不能为空';
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = '邮箱格式不正确';
}
if (!formData.password) newErrors.password = '密码不能为空';
else if (formData.password.length < 6) {
newErrors.password = '密码至少6位';
}
return newErrors;
};
const handleSubmit = (event) => {
event.preventDefault();
const validationErrors = validate();
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
// 提交表单
console.log('验证通过,提交数据');
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
placeholder="邮箱"
/>
{errors.email && <span style={{color: 'red'}}>{errors.email}</span>}
</div>
<div>
<input
type="password"
value={formData.password}
onChange={(e) => setFormData({...formData, password: e.target.value})}
placeholder="密码"
/>
{errors.password && <span style={{color: 'red'}}>{errors.password}</span>}
</div>
<button type="submit">提交</button>
</form>
);
}
补充说明:
- 推荐使用受控组件,便于实现表单验证和动态行为
- 合成事件系统提供了事件池机制,事件对象会被复用
- 文件上传通常使用非受控组件
React 列表、Key 与条件渲染
包含的题目: 45. 如何渲染列表? 46. key 的作用是什么?(高频考点) 47. 如何选择 key? 48. 什么是条件渲染? 49. 条件渲染的方式有哪些?
详细答案:
- 列表渲染:
function ListComponent() {
const items = ['Apple', 'Banana', 'Orange'];
// 使用 map 渲染列表
return (
<ul>
{items.map((item, index) => (
<li key={item.id || index}>{item}</li>
))}
</ul>
);
}
// 复杂列表
const todos = [
{ id: 1, text: '学习 React', completed: true },
{ id: 2, text: '学习 Hooks', completed: false },
{ id: 3, text: '构建项目', completed: false }
];
function TodoList() {
return (
<ul>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
}
-
key 的作用:
- 标识元素:帮助 React 识别哪些元素发生了变化
- 性能优化:减少不必要的 DOM 操作
- 状态保持:确保组件状态在重新渲染时保持正确
-
key 选择原则:
- 使用唯一且稳定的标识符,如 item.id
- 避免使用数组索引作为 key:当列表顺序变化时会导致性能问题
- 如果没有 id,可以使用其他唯一值(如 item.name + index)
// ✅ 正确:使用唯一 id
const listItems = data.map(item => (
<li key={item.id}>{item.name}</li>
));
// ⚠️ 谨慎使用:数组索引(仅当列表不会重新排序时)
const listItems = data.map((item, index) => (
<li key={index}>{item.name}</li>
));
// ❌ 错误:使用随机数(每次渲染都会改变)
const listItems = data.map(item => (
<li key={Math.random()}>{item.name}</li>
));
- 条件渲染的多种方式:
function ConditionalRendering() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userRole, setUserRole] = useState('user');
const [messages, setMessages] = useState(['消息1', '消息2']);
// 1. if 语句
let content;
if (isLoggedIn) {
content = <Dashboard />;
} else {
content = <LoginForm />;
}
// 2. 三元运算符
return (
<div>
{/* 方式1:if 语句结果 */}
{content}
{/* 方式2:三元运算符 */}
{isLoggedIn ? <Dashboard /> : <LoginForm />}
{/* 方式3:逻辑与运算符 */}
{isLoggedIn && <Dashboard />}
{/* 方式4:立即执行函数 */}
{(() => {
switch (userRole) {
case 'admin': return <AdminPanel />;
case 'user': return <UserPanel />;
default: return <GuestPanel />;
}
})()}
{/* 方式5:变量赋值 */}
{messages.length > 0 && (
<MessageList messages={messages} />
)}
{/* 方式6:组件内条件渲染 */}
<ConditionalComponent show={isLoggedIn} />
</div>
);
}
// 组件内条件渲染
function ConditionalComponent({ show }) {
if (!show) return null;
return <div>条件渲染的内容</div>;
}
补充说明:
- key 是 React 列表渲染的重要组成部分,必须提供
- 条件渲染可以根据不同场景选择合适的方式
- 对于复杂的条件逻辑,可以提取为单独的组件或函数
React 组件通信
包含的题目: 50. 组件通信的方法有哪些?(高频考点) 51. 父子组件通信的方法 52. 兄弟组件通信的方法 53. 跨级组件通信的方法 54. Context API 的使用(高频考点)
详细答案:
- 父子组件通信:
// 父组件 → 子组件:通过 props 传递数据
function Parent() {
const [count, setCount] = useState(0);
// 子组件 → 父组件:通过回调函数
const handleChildClick = (valueFromChild) => {
console.log('来自子组件的值:', valueFromChild);
setCount(valueFromChild);
};
return (
<div>
<h3>父组件计数: {count}</h3>
<Child
count={count} // 父 → 子:传递数据
onChildClick={handleChildClick} // 父 → 子:传递回调函数
/>
</div>
);
}
// 子组件
function Child({ count, onChildClick }) {
const handleClick = () => {
const newValue = count + 1;
onChildClick(newValue); // 子 → 父:调用回调函数
};
return (
<div>
<p>从父组件接收的计数: {count}</p>
<button onClick={handleClick}>增加计数</button>
</div>
);
}
- 兄弟组件通信:
// 方式1:通过共同的父组件(状态提升)
function Parent() {
const [sharedState, setSharedState] = useState('');
return (
<div>
<SiblingA sharedState={sharedState} setSharedState={setSharedState} />
<SiblingB sharedState={sharedState} setSharedState={setSharedState} />
</div>
);
}
// 方式2:使用 Context
const SharedContext = React.createContext();
function App() {
const [sharedState, setSharedState] = useState('');
return (
<SharedContext.Provider value={{ sharedState, setSharedState }}>
<SiblingA />
<SiblingB />
</SharedContext.Provider>
);
}
function SiblingA() {
const { sharedState, setSharedState } = useContext(SharedContext);
// 使用 sharedState 和 setSharedState
}
- 跨级组件通信:
// 方式1:Context API(推荐)
const ThemeContext = React.createContext('light'); // 默认值
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button style={{ background: theme === 'dark' ? '#333' : '#FFF' }}>主题按钮</button>;
}
// 方式2:逐层传递 props(prop drilling)
function App() {
const theme = 'dark';
return <Toolbar theme={theme} />;
}
function Toolbar({ theme }) {
return <ThemedButton theme={theme} />;
}
function ThemedButton({ theme }) {
// 使用 theme
}
- Context API 详细使用:
// 创建 Context
const UserContext = React.createContext({
user: null,
login: () => {},
logout: () => {}
});
// Provider 组件
function App() {
const [user, setUser] = useState(null);
const login = (userData) => {
setUser(userData);
localStorage.setItem('user', JSON.stringify(userData));
};
const logout = () => {
setUser(null);
localStorage.removeItem('user');
};
return (
<UserContext.Provider value={{ user, login, logout }}>
<Header />
<MainContent />
<Footer />
</UserContext.Provider>
);
}
// 使用 Context
function Header() {
const { user, logout } = useContext(UserContext);
return (
<header>
{user ? (
<>
<span>欢迎, {user.name}</span>
<button onClick={logout}>退出登录</button>
</>
) : (
<span>请登录</span>
)}
</header>
);
}
// 使用 Consumer(类组件或不想使用 Hook 时)
function OldWayComponent() {
return (
<UserContext.Consumer>
{({ user }) => (
<div>当前用户: {user ? user.name : '未登录'}</div>
)}
</UserContext.Consumer>
);
}
补充说明:
- Props 适用于父子组件通信
- Context 适用于跨多级组件通信,避免 prop drilling
- 复杂状态管理建议使用 Redux、MobX 等专业库
- 适度使用 Context,避免过度设计
React 路由
包含的题目: 55. 什么是 React Router? 56. 路由的基本使用方法 57. BrowserRouter 与 HashRouter 的区别 58. 动态路由的使用 59. 嵌套路由的使用 60. 路由守卫的使用 61. 路由的懒加载实现
详细答案:
-
React Router 是 React 最流行的路由库,提供了声明式的路由配置和导航功能。
-
基本使用:
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="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
- BrowserRouter vs HashRouter:
| 特点 | BrowserRouter | HashRouter |
|---|---|---|
| URL 格式 | http://example.com/about | http://example.com/#/about |
| 原理 | 使用 HTML5 History API | 使用 URL hash |
| 服务器要求 | 需要服务器配置支持 | 无需服务器配置 |
| SEO 友好 | 更友好 | 较差 |
| 推荐场景 | 现代应用,有服务器控制权 | 静态站点,无服务器控制权 |
- 动态路由:
function App() {
return (
<Routes>
<Route path="/users/:id" element={<UserDetail />} />
<Route path="/products/:category/:id" element={<ProductDetail />} />
</Routes>
);
}
function UserDetail() {
const { id } = useParams(); // 获取路由参数
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${id}`).then(res => res.json()).then(setUser);
}, [id]);
return <div>用户 ID: {id}</div>;
}
- 嵌套路由:
function App() {
return (
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
function DashboardLayout() {
return (
<div>
<h2>仪表板</h2>
<nav>
<Link to="/dashboard">首页</Link>
<Link to="/dashboard/profile">个人资料</Link>
<Link to="/dashboard/settings">设置</Link>
</nav>
<Outlet /> {/* 子路由渲染的位置 */}
</div>
);
}
- 路由守卫:
// 保护路由组件
function PrivateRoute({ children }) {
const { user } = useContext(UserContext);
const location = useLocation();
if (!user) {
// 重定向到登录页,并保存当前路径以便登录后返回
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 使用
<Route
path="/admin"
element={
<PrivateRoute>
<AdminPanel />
</PrivateRoute>
}
/>
// 登录组件中处理重定向
function Login() {
const { login } = useContext(UserContext);
const location = useLocation();
const navigate = useNavigate();
const from = location.state?.from?.pathname || '/';
const handleLogin = () => {
// 登录逻辑
login(userData);
navigate(from, { replace: true }); // 登录后跳转回原页面
};
return <button onClick={handleLogin}>登录</button>;
}
- 路由懒加载:
import { lazy, Suspense } from 'react';
// 懒加载组件
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
const Users = lazy(() => import('./Users'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
补充说明:
- React Router v6 引入了新的 API,如 Routes、Route、Outlet
- 路由懒加载可以减小初始包体积,提升首屏加载速度
- 嵌套路由适合有公共布局的页面结构
React 状态管理
包含的题目: 62. 什么是 Redux?(高频考点) 63. Redux 的核心概念有哪些? 64. Redux 的三大原则是什么? 65. react-redux 的使用 66. useSelector、useDispatch Hook 的使用 67. Redux Toolkit 的使用 68. Redux Thunk 的使用 69. MobX 的核心概念有哪些? 70. 如何选择状态管理方案?
详细答案:
-
Redux 是一个可预测的状态容器,主要用于管理应用中的共享状态。
-
Redux 核心概念:
- Store:单一状态树,存储整个应用的状态
- Action:描述发生了什么的对象,必须有 type 属性
- Reducer:纯函数,根据 Action 更新 State
- Dispatch:触发 Action 的方法
- Middleware:扩展 Redux 功能的中间件
-
Redux 三大原则:
- 单一数据源:整个应用的状态存储在一个 Store 中
- State 只读:只能通过 Action 修改 State
- 使用纯函数修改:Reducer 必须是纯函数
-
Redux 基础实现:
// 1. 定义 Action Types
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// 2. 创建 Action Creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });
// 3. 创建 Reducer
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case INCREMENT:
return { ...state, count: state.count + 1 };
case DECREMENT:
return { ...state, count: state.count - 1 };
default:
return state;
}
}
// 4. 创建 Store
import { createStore } from 'redux';
const store = createStore(counterReducer);
// 5. 使用
store.dispatch(increment());
console.log(store.getState()); // { count: 1 }
- react-redux 使用:
import { Provider } from 'react-redux';
function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}
// 使用 Hooks
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(推荐):
import { configureStore, createSlice } from '@reduxjs/toolkit';
// 创建 Slice
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
increment: (state) => {
state.count += 1; // 使用 Immer,可以直接修改
},
decrement: (state) => {
state.count -= 1;
},
incrementByAmount: (state, action) => {
state.count += action.payload;
}
}
});
// 导出 Action
export const { increment, decrement } = counterSlice.actions;
// 创建 Store
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
// 使用
dispatch(increment());
dispatch(incrementByAmount(5));
- Redux Thunk(异步操作):
import { createAsyncThunk } from '@reduxjs/toolkit';
// 创建异步 Thunk
export const fetchUser = createAsyncThunk(
'user/fetchUser',
async (userId, thunkAPI) => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
return thunkAPI.rejectWithValue(await response.json());
}
return response.json();
}
);
// 在 Slice 中处理
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.payload;
});
}
});
- 其他状态管理方案对比:
| 方案 | 特点 | 适用场景 |
|---|---|---|
| Context + useReducer | 内置,轻量 | 小型应用,简单状态共享 |
| Redux | 严格流程,可预测 | 大型应用,复杂状态管理 |
| MobX | 响应式,简单直观 | 中等规模,需要灵活性的应用 |
| Zustand | 简单,轻量 | 小型到中型应用 |
| Recoil | 原子状态,实验性 | 需要细粒度状态管理的应用 |
补充说明:
- Redux Toolkit 是 Redux 官方推荐的工具集,简化了 Redux 使用
- 对于大多数应用,Context API 可能已经足够
- 根据项目复杂度选择合适的方案
React 性能优化
包含的题目: 71. React 性能优化的方法有哪些?(高频考点) 72. shouldComponentUpdate 的使用 73. PureComponent 的使用 74. React.memo 的使用(高频考点) 75. 代码分割的实现 76. 懒加载的实现 77. 虚拟列表的实现
详细答案:
- React.memo 优化组件重渲染:
// 普通组件:每次父组件重渲染,子组件也会重渲染
function Child({ name }) {
console.log('Child 渲染');
return <div>{name}</div>;
}
// 使用 React.memo:只有当 props 变化时才重渲染
const MemoizedChild = React.memo(function Child({ name }) {
console.log('MemoizedChild 渲染(仅当 props 变化时)');
return <div>{name}</div>;
});
// 自定义比较函数
const MemoizedChild2 = React.memo(
Child,
(prevProps, nextProps) => {
// 返回 true 表示 props 相等,跳过渲染
return prevProps.name === nextProps.name;
}
);
- useCallback 与 useMemo:
function Parent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// ❌ 有问题:每次渲染都会创建新函数,导致子组件重渲染
// const handleClick = () => console.log(count);
// ✅ 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
console.log('点击', count);
}, [count]); // 依赖变化时重新创建
// ✅ 使用 useMemo 缓存计算结果
const expensiveValue = useMemo(() => {
console.log('计算 expensiveValue');
return text.split('').reverse().join('');
}, [text]); // 仅当 text 变化时重新计算
return (
<div>
<Child onClick={handleClick} />
<p>反转文本: {expensiveValue}</p>
</div>
);
}
- 代码分割与懒加载:
import { lazy, Suspense } from 'react';
// 懒加载组件
const Dashboard = lazy(() => import('./Dashboard'));
const AdminPanel = lazy(() => import('./AdminPanel'));
function App() {
const [showDashboard, setShowDashboard] = useState(false);
return (
<div>
<button onClick={() => setShowDashboard(true)}>
加载仪表板
</button>
{showDashboard && (
<Suspense fallback={<div>加载中...</div>}>
<Dashboard />
</Suspense>
)}
</div>
);
}
// 路由懒加载
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
function RouterApp() {
return (
<BrowserRouter>
<Suspense fallback={<div>页面加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
- 虚拟列表:
import { FixedSizeList as List } from 'react-window';
// 大数据量列表优化
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
第 {index + 1} 行: {items[index]}
</div>
);
return (
<List
height={400} // 列表可视高度
itemCount={items.length} // 总项数
itemSize={50} // 每项高度
width={300} // 列表宽度
>
{Row}
</List>
);
}
// 自定义虚拟列表实现
function CustomVirtualList({ items, itemHeight, visibleCount }) {
const [scrollTop, setScrollTop] = useState(0);
const containerRef = useRef(null);
const totalHeight = items.length * itemHeight;
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(startIndex + visibleCount, items.length - 1);
const visibleItems = items.slice(startIndex, endIndex + 1);
return (
<div
ref={containerRef}
style={{ height: visibleCount * itemHeight, overflow: 'auto' }}
onScroll={(e) => setScrollTop(e.target.scrollTop)}
>
<div style={{ height: totalHeight, position: 'relative' }}>
{visibleItems.map((item, index) => (
<div
key={startIndex + index}
style={{
position: 'absolute',
top: (startIndex + index) * itemHeight,
height: itemHeight,
width: '100%'
}}
>
{item}
</div>
))}
</div>
</div>
);
}
- 其他优化技巧:
// 1. 避免内联对象和函数
function OptimizedComponent() {
// ❌ 不好:每次渲染创建新对象
// return <Child style={{ color: 'red' }} onClick={() => {}} />;
// ✅ 好:使用 useMemo 和 useCallback
const style = useMemo(() => ({ color: 'red' }), []);
const handleClick = useCallback(() => {}, []);
return <Child style={style} onClick={handleClick} />;
}
// 2. 列表项使用 key
const items = data.map(item => (
<ListItem key={item.id} item={item} />
));
// 3. 按需渲染大量数据
function LargeDataComponent() {
const [visibleItems, setVisibleItems] = useState(10);
const loadMore = useCallback(() => {
setVisibleItems(prev => prev + 10);
}, []);
return (
<div>
{data.slice(0, visibleItems).map(item => (
<Item key={item.id} item={item} />
))}
<button onClick={loadMore}>加载更多</button>
</div>
);
}
// 4. 使用生产环境构建
// package.json
// "build": "react-scripts build" // 自动优化
// 5. 图片懒加载
import { LazyLoadImage } from 'react-lazy-load-image-component';
function ImageGallery() {
return (
<div>
<LazyLoadImage
src="image.jpg"
alt="图片"
effect="blur" // 加载时模糊效果
placeholderSrc="placeholder.jpg"
/>
</div>
);
}
补充说明:
- 使用 React DevTools Profiler 分析性能瓶颈
- 优先优化重渲染次数多的组件
- 代码分割可以显著提升首屏加载速度
React 常见问题
包含的题目: 78. setState 是同步还是异步的?(高频考点) 79. setState 的合并机制是什么? 80. useEffect 的执行时机是什么? 81. 虚拟 DOM 的 Diff 算法是什么?(高频考点) 82. React 的合成事件是什么? 83. React 的 Fiber 架构是什么?(高频考点) 84. React 的错误边界是什么? 85. React 的 Suspense 是什么? 86. React 的并发模式是什么? 87. React 的服务端渲染是什么? 88. Next.js 的使用
详细答案:
- setState 的同步与异步:
class Example extends React.Component {
state = { count: 0 };
handleClick = () => {
// 在 React 事件处理函数中:异步更新
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 0,不是 1
// 获取更新后的值
this.setState({ count: this.state.count + 1 }, () => {
console.log('回调中获取:', this.state.count); // 1
});
// 函数式更新,基于前一个状态
this.setState(prevState => ({ count: prevState.count + 1 }));
};
componentDidMount() {
// 在 setTimeout、原生事件中:同步更新
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 1,是更新后的值
}, 0);
// 原生事件
document.getElementById('btn').addEventListener('click', () => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 1,同步更新
});
}
}
setState 合并机制:React 会将同一事件循环中的多个 setState 调用合并为一次更新。
// 三个 setState 调用会被合并
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
// 最终 count 只增加 1,不是 3
// 使用函数式更新解决
this.setState(prev => ({ count: prev.count + 1 }));
this.setState(prev => ({ count: prev.count + 1 }));
this.setState(prev => ({ count: prev.count + 1 }));
// 最终 count 增加 3
- useEffect 执行时机:
function Example() {
useEffect(() => {
// 在浏览器完成布局和绘制后执行
// 不会阻塞浏览器渲染
console.log('useEffect 执行');
return () => {
// 清理函数在下一次 effect 执行前运行
console.log('清理函数执行');
};
});
// useLayoutEffect:在 DOM 更新后、浏览器绘制前执行
useLayoutEffect(() => {
// 会阻塞浏览器渲染
// 适合 DOM 测量、同步更新等场景
console.log('useLayoutEffect 执行');
});
return <div>组件</div>;
}
- 虚拟 DOM Diff 算法:
- Tree Diff:跨层级的移动操作很少,React 只会对相同层级的节点进行比较
- Component Diff:相同类型的组件继续比较,不同类型则直接替换
- Element Diff:同一层级的节点使用 key 进行标识
Diff 策略:
- 深度优先遍历,只比较相同层级
- 节点类型不同,直接销毁重建
- 节点类型相同,更新属性
- 列表使用 key 优化移动操作
- Fiber 架构:
- 可中断的渲染:将渲染工作分割成小单元,可以暂停、继续
- 优先级调度:高优先级更新(如用户输入)优先处理
- 并发模式:允许多个状态更新同时进行
Fiber 节点结构:
{
type: 'div', // 组件类型
key: null, // key
child: Fiber, // 第一个子节点
sibling: Fiber, // 下一个兄弟节点
return: Fiber, // 父节点
alternate: Fiber, // 上一次渲染的 Fiber
memoizedProps: {}, // 上一次渲染的 props
memoizedState: {}, // 上一次渲染的 state
updateQueue: [] // 更新队列
}
- 错误边界:
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
// 更新 state 显示降级 UI
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 记录错误信息
console.error('组件错误:', error, errorInfo);
// 可以发送错误到监控系统
}
render() {
if (this.state.hasError) {
return <h1>出错了: {this.state.error.message}</h1>;
}
return this.props.children;
}
}
// 使用
<ErrorBoundary>
<BuggyComponent />
</ErrorBoundary>
- Suspense:
// 数据获取的 Suspense
const resource = fetchProfileData();
function ProfilePage() {
return (
<Suspense fallback={<h1>加载个人资料...</h1>}>
<ProfileDetails />
<Suspense fallback={<h1>加载文章列表...</h1>}>
<ProfileTimeline />
</Suspense>
</Suspense>
);
}
function ProfileDetails() {
const user = resource.user.read(); // 如果数据未就绪,会抛出 Promise
return <h1>{user.name}</h1>;
}
- 服务端渲染(SSR):
// 服务端
import { renderToString } from 'react-dom/server';
function handleRequest(req, res) {
const html = renderToString(<App />);
res.send(`
<!DOCTYPE html>
<html>
<head><title>SSR App</title></head>
<body>
<div id="root">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`);
}
- Next.js 使用:
// pages/index.js
export default function HomePage() {
return <h1>欢迎使用 Next.js</h1>;
}
// 页面跳转
import Link from 'next/link';
function Navigation() {
return (
<nav>
<Link href="/">首页</Link>
<Link href="/about">关于</Link>
</nav>
);
}
// 获取数据
export async function getServerSideProps(context) {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } };
}
补充说明:
- React 18 引入了并发特性,如 startTransition
- Suspense 不仅仅用于代码分割,也用于数据获取
- Next.js 是流行的 React 框架,提供了完善的 SSR 支持
以上是对 React 88 道面试题目的综合解答,涵盖了 React 的核心概念、Hooks、组件通信、状态管理、性能优化和高级特性。每个主题都包含了理论说明、代码示例和最佳实践建议。