react 面试题(手把手讲解)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
前言
在前端开发领域,React 作为主流框架之一,一直是面试中高频考察的技术点。无论是前端初级工程师还是中级开发者,掌握 React 核心概念、组件通信机制和性能优化技巧,都是应对面试的关键。本文将围绕 “React 面试题” 这一主题,从基础到进阶,结合实际案例和代码示例,帮助读者系统梳理常见问题,并通过形象的比喻加深理解,最终提升面试竞争力。
一、React 核心概念与基础面试题
1.1 什么是 React?
React 是由 Facebook 开发的声明式 JavaScript 库,主要用于构建用户界面。它采用组件化设计,将 UI 拆分为可复用的组件,并通过 虚拟 DOM(Virtual DOM) 实现高效更新。
比喻:可以将 React 比作乐高积木,每个组件就像一块积木,通过组合和嵌套构建复杂的界面,而虚拟 DOM 则像一个“智能助手”,负责计算并优化真实 DOM 的更新。
1.2 虚拟 DOM 是如何工作的?
虚拟 DOM 是 React 的核心机制之一,其工作流程如下:
- 创建虚拟节点:将 UI 转换为轻量级的 JavaScript 对象树。
- 计算差异(Diffing):对比新旧虚拟 DOM 树的差异,确定需要更新的部分。
- 批量更新真实 DOM:仅对差异部分进行渲染,减少直接操作 DOM 的性能损耗。
代码示例:
// 示例:修改状态后触发虚拟 DOM 更新
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 }); // 触发虚拟 DOM 更新
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>+1</button>
</div>
);
}
}
1.3 类组件与函数组件的区别
- 类组件(Class Component):基于 ES6 类,需继承
React.Component
,支持state
和生命周期方法
。 - 函数组件(Functional Component):纯函数形式,通过
props
接收数据,结合 Hooks(如useState
、useEffect
)实现状态管理。
面试考察点:通常会要求对比两者的优缺点,例如函数组件更简洁,而类组件适合复杂逻辑。
二、组件通信与状态管理
2.1 父组件向子组件传递数据
父组件通过 props 将数据传递给子组件,子组件通过 props
接收。
案例:父组件控制子组件的显示内容。
// 父组件
function Parent() {
const [message, setMessage] = useState("Hello");
return <Child message={message} />;
}
// 子组件
function Child({ message }) {
return <div>{message}</div>; // 通过 props 获取数据
}
2.2 子组件向父组件传递数据
通过 回调函数作为 props 实现数据反向流动。
案例:子组件触发父组件状态更新。
// 父组件
function Parent() {
const [count, setCount] = useState(0);
const handleIncrement = () => setCount(count + 1);
return (
<Child onIncrement={handleIncrement} />
);
}
// 子组件
function Child({ onIncrement }) {
return <button onClick={onIncrement}>+1</button>;
}
2.3 跨级组件通信:Context API
当组件层级较深时,使用 Context API 避免“属性钻取”(Prop Drilling)。
步骤:
- 创建 Context:
const MyContext = React.createContext(initialValue);
- 提供数据:通过
<MyContext.Provider value={data}>
包裹子组件。 - 消费数据:通过
useContext(MyContext)
或Consumer
获取。
代码示例:
// 创建 Context
const ThemeContext = React.createContext();
// 提供者组件
function ThemeProvider({ children }) {
const theme = { color: "blue" };
return (
<ThemeContext.Provider value={theme}>
{children}
</ThemeContext.Provider>
);
}
// 消费组件
function DisplayTheme() {
const theme = useContext(ThemeContext);
return <div style={{ color: theme.color }}>主题颜色</div>;
}
三、React 生命周期与 Hook 机制
3.1 类组件的生命周期方法
React 16.x 版本前的生命周期方法分为三个阶段:
| 阶段 | 方法 | 作用 |
|------|------|------|
| 挂载阶段 | constructor()
| 初始化状态 |
| componentDidMount()
| 组件首次渲染完成后执行 |
| 更新阶段 | componentDidUpdate()
| 状态/属性更新后执行(首次不触发) |
| 卸载阶段 | componentWillUnmount()
| 组件销毁前清理资源 |
比喻:生命周期如同人的成长阶段,从出生(挂载)到成长(更新)再到结束(卸载)。
3.2 React Hook 的核心 API
Hooks 允许函数组件拥有状态和生命周期功能,常见 Hook 包括:
useState
:管理组件内部状态。useEffect
:处理副作用(如数据请求、订阅)。useContext
:消费 Context 数据。
面试高频问题:
Q:
useEffect
的第二个参数dependencies
是什么作用?
A:控制副作用触发的条件,空数组[]
表示仅在挂载和卸载时执行。
四、性能优化与常见陷阱
4.1 避免不必要的渲染
- 使用
React.memo
:包装组件,防止无状态变化时重复渲染。 - Memoization:通过
useMemo
或useCallback
缓存计算结果或函数。
代码示例:
// 使用 React.memo 阻止子组件重复渲染
const MemoizedChild = React.memo(({ data }) => {
console.log("Child rendered"); // 仅在 data 变化时触发
return <div>{data}</div>;
});
4.2 关键渲染性能问题
- 问题:直接修改状态对象(如
this.state.items = [...items]
)会绕过 React 的状态更新机制,导致组件不更新。 - 解决方案:使用
setState
并返回新对象,例如:this.setState(prevState => ({ items: [...prevState.items, newItem] }));
五、进阶知识点与高频面试场景
5.1 高阶组件(HOC)
HOC 是一个接受组件并返回新组件的函数,常用于复用逻辑(如权限验证)。
案例:封装登录状态验证的 HOC。
function withAuth(WrappedComponent) {
return function AuthComponent(props) {
const isAuthenticated = checkAuth(); // 自定义验证逻辑
return isAuthenticated ? <WrappedComponent {...props} /> : <Login />;
};
}
5.2 React 事件系统
React 的事件处理采用 合成事件(SyntheticEvent),与原生事件不同:
- 区别:合成事件是跨浏览器封装,且需手动阻止默认行为(如
e.preventDefault()
)。 - 面试问题:
Q:为什么不能在
useEffect
中直接使用e
变量?
A:因为useEffect
的依赖项未包含e
,可能导致闭包引用过期的旧值。
六、常见陷阱与解决方案
6.1 错误处理:componentDidCatch
与 ErrorBoundary
通过 错误边界组件 捕获子组件的异常,避免整个应用崩溃。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true }; // 更新状态标记错误
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
6.2 防止内存泄漏:清理副作用
在 useEffect
或 componentWillUnmount
中清理定时器、订阅等资源。
useEffect(() => {
const interval = setInterval(() => {}, 1000);
return () => clearInterval(interval); // 清理函数
}, []);
结论
本文围绕 “React 面试题” 分析了从基础到进阶的核心知识点,涵盖组件通信、状态管理、性能优化和常见陷阱。通过代码示例和比喻,帮助读者在理解原理的同时,掌握实际开发中的最佳实践。面试中,除了技术细节,清晰的逻辑表达和解决问题的思路同样重要。建议读者结合项目实践,深入理解 React 的设计理念,并持续关注新特性(如 React 18 的并发模式),以应对面试中的挑战。
(字数统计:约 1800 字)