这里是超详细的 React 前端面试"八股文",涵盖了从核心概念到高级技巧的高频考点。
一、核心概念
1. JSX
- 是什么:JavaScript XML,一种 JavaScript 的语法扩展,允许在 JS 中写类似 HTML 的结构。
- 本质 :JSX 会被 Babel 编译成
React.createElement()函数调用,最终返回一个描述 DOM 结构的 JavaScript 对象(即 React元素)。 - 注意事项 :
- 必须有一个根节点 (或使用
<>空标签)。 - 所有标签必须闭合。
- 使用
className代替class,htmlFor代替for。 - 使用驼峰命名 (如
onClick)。 - 可以在
{}内嵌入 JavaScript 表达式。
- 必须有一个根节点 (或使用
2. 组件
-
函数组件 :现代 React 开发的主流。
javascriptfunction Welcome(props) { return <h1>Hello, {props.name}</h1>; } // 或使用箭头函数 const Welcome = (props) => <h1>Hello, {props.name}</h1>; -
类组件 :
javascriptclass Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
二、数据流与状态管理
1. Props(属性)
- 特点 :只读的,从父组件流向子组件。使组件具有可配置性。
- Children Prop :
props.children可以获取到组件开闭标签之间的内容。 - PropTypes:用于类型检查(现在更推荐 TypeScript)。
2. State(状态)
-
特点 :组件内部可变的数据,状态改变会触发组件重新渲染。
-
类组件 State :在构造函数中初始化,使用
this.setState()更新(是异步的)。 -
函数组件 State :使用
useStateHook 。javascriptconst [count, setCount] = useState(0); // 初始值为 0 setCount(1); // 直接设置新值 setCount(prevCount => prevCount + 1); // 使用函数式更新,依赖于前一个状态
Props 与 State 的区别:
| 维度 | Props | State |
|---|---|---|
| 来源 | 父组件传递 | 组件内部定义 |
| 可变性 | 只读 | 可变 |
| 用途 | 传递数据和回调函数 | 管理组件内部状态 |
三、事件处理与 this 指向(类组件)
在类组件中,事件处理函数的 this 默认是 undefined。
解决方法:
-
在构造函数中绑定 (推荐):
javascriptconstructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } -
使用 Class Fields 语法 (实验性,但常用):
javascripthandleClick = () => { // 这里的 this 指向正确 }; -
在回调中使用箭头函数 (不推荐在 render 中,会导致每次渲染都创建新函数):
javascriptonClick={() => this.handleClick()}
向事件处理函数传递参数:
javascript
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
四、组件生命周期(类组件)
挂载:
constructor()static getDerivedStateFromProps(props, state)(不常用)render()(必须纯函数)componentDidMount()-> 发起 AJAX 请求、DOM 操作、订阅事件
更新 (由 props 或 state 变化引起):
static getDerivedStateFromProps(props, state)shouldComponentUpdate(nextProps, nextState)-> 性能优化关键,决定是否重新渲染render()getSnapshotBeforeUpdate(prevProps, prevState)componentDidUpdate(prevProps, prevState, snapshot)-> 可执行 DOM 操作
卸载:
componentWillUnmount()-> 执行清理工作,如清除定时器、取消网络请求、取消订阅
五、Hooks(函数组件的灵魂)
Hooks 让函数组件拥有状态和生命周期等能力。
1. useState
- 用于在函数组件中添加状态。
- 注意 :状态更新函数是异步的,多次 setState 在同一个事件循环中可能会被合并(批处理)。
2. useEffect
- 用于执行副作用(数据获取、订阅、手动修改 DOM)。
- 相当于三个生命周期的组合 :
componentDidMount+componentDidUpdate+componentWillUnmount。
javascript
import { useEffect } from 'react';
// 1. 每次渲染后都执行
useEffect(() => {
// ... 副作用逻辑
});
// 2. 只在挂载后执行一次 (依赖项为空数组)
useEffect(() => {
// ... 相当于 componentDidMount
}, []);
// 3. 在挂载后和 count 变化后执行
useEffect(() => {
// ... 副作用逻辑
}, [count]); // 依赖项数组
// 4. 需要清理的副作用(如订阅、定时器)
useEffect(() => {
const subscription = props.source.subscribe();
// 返回一个清理函数,在组件卸载和下一次副作用执行前调用
return () => {
subscription.unsubscribe();
};
}, [props.source]);
3. useContext
- 用于在组件树中跨层级传递数据,避免 props 逐层传递。
javascript
const ThemeContext = createContext('light');
// 顶层组件
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
// 底层组件
const theme = useContext(ThemeContext);
4. useReducer
useState的替代方案,适用于复杂状态逻辑(多个子值或下一个状态依赖于之前的状态)。
5. useCallback 与 useMemo(性能优化)
-
useCallback:缓存函数本身,避免子组件因函数引用不同而无效重渲染。javascriptconst memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]); -
useMemo:缓存计算值 ,避免每次渲染都进行高开销计算。javascriptconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
6. useRef
-
用于获取 DOM 元素或存储一个可变值 ,该值在组件的整个生命周期内保持不变,且更新不会触发重新渲染。
javascriptconst inputEl = useRef(null); const onButtonClick = () => { inputEl.current.focus(); }; return <input ref={inputEl} type="text" />;
六、虚拟 DOM 与 Diff 算法
1. 什么是虚拟 DOM?
- 一个轻量的 JavaScript 对象,是真实 DOM 的抽象。通过
React.createElement创建。
2. Diffing 策略
React 的 Diff 算法基于两个假设:
- 不同类型的元素会产生不同的树 (如
<div>变<span>,会直接拆卸重建)。 - 通过
keyProp 来标识哪些子元素在不同的渲染中保持稳定。
Diff 过程:
- 树对比:对树进行分层比较,两棵树只会对同一层次的节点进行比较。
- 组件对比:如果是同一类型的组件,继续对比其子节点/VNode。如果不是,则直接替换整个组件下的所有子节点。
- 元素对比 :对于同一层级的同类型元素(如
<div>),React 会复用节点,只更新有变化的属性。 - 列表对比 :对于列表元素,
key至关重要。React 使用 key 来匹配新旧树中的子元素,使元素在重新排序时能够高效地移动,而不是销毁和重建。
3. Key 的作用与为什么不要用 index
- 作用:帮助 React 识别哪些项被修改、添加或删除,是元素的唯一标识。
- 为什么不用 index :
- 如果列表顺序变化,index 也会变,导致 key 变化,React 会错误地认为需要重新渲染元素,性能变差。
- 可能导致状态错乱(例如,在受控输入框的列表中,打乱顺序后输入框的内容会和错误的项绑定)。
七、状态提升与组件通信
- 状态提升:当多个组件需要反映相同的变化数据时,建议将共享状态提升到最近的共同父组件中去。
- 父子通信 :
- 父 -> 子:通过 Props。
- 子 -> 父:父组件通过 Props 传递一个回调函数给子组件,子组件调用该函数来向父组件传递信息。
- 兄弟通信:状态提升到共同的父组件。
- 跨层级/复杂应用通信 :
- Context API:适合传递"全局"数据(如主题、用户信息)。
- 状态管理库 :Redux (经典,生态成熟),Zustand (轻量简单),MobX(响应式)。
八、受控组件 vs 非受控组件
| 维度 | 受控组件 | 非受控组件 |
|---|---|---|
| 定义 | 表单数据由 React 组件状态管理 | 表单数据由 DOM 自身管理 |
| 值控制 | value={this.state.value} |
defaultValue 或 ref |
| 变化处理 | onChange 事件更新 state |
通过 ref 从 DOM 节点获取值 |
| 推荐度 | 推荐,数据流清晰 | 在需要集成非 React 代码或文件上传时使用 |
示例:
javascript
// 受控组件
const [inputValue, setInputValue] = useState('');
<input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
// 非受控组件
const inputRef = useRef();
<input type="text" defaultValue="hello" ref={inputRef} />
// 通过 inputRef.current.value 获取值
九、高阶组件与 Render Props(已逐渐被 Hooks 替代)
- 高阶组件 :一个函数,接受一个组件,返回一个新的增强组件。用于逻辑复用。
- Render Props:一个值为函数的 Prop,该函数返回一个 React 元素并告知组件要渲染什么。组件通过调用这个函数来渲染内容。
Hooks 已成为逻辑复用的首选方案,因为它们更简洁,避免了 HOC 的嵌套地狱和 Render Props 的回调地狱。
十、常见面试题与手写题
-
React 事件机制(合成事件)?
- React 使用事件委托 ,将所有事件绑定到
document(v17+ 绑定到root容器)上,而不是每个 DOM 节点。 - 提供与原生事件相同的接口,但跨浏览器兼容。
- 在事件处理函数执行完后,合成事件对象会被清空,无法被异步访问(如需异步访问,需调用
e.persist())。
- React 使用事件委托 ,将所有事件绑定到
-
setState是同步还是异步?- 在React 事件处理函数和生命周期 中,
setState是异步的(批处理更新)。 - 在
setTimeout、原生事件监听器 等异步代码中,setState是同步的。
- 在React 事件处理函数和生命周期 中,
-
React 性能优化手段?
React.memo:缓存函数组件,对 Props 进行浅比较,避免不必要的重渲染。useMemo/useCallback:缓存值和函数。shouldComponentUpdate(类组件)或React.PureComponent。- 代码分割:
React.lazy+Suspense。
-
什么是 Fiber?
- React 16 引入的新的协调引擎(Reconciliation)。
- 目的 :将渲染工作拆分成小的单元(Fiber 节点),并允许在浏览器空闲时执行,实现可中断的异步渲染,解决大型应用卡顿问题。
-
手写
useState简易版:javascriptlet state = []; let setters = []; let stateIndex = 0; function createSetter(index) { return newValue => { state[index] = newValue; render(); // 触发重新渲染 }; } function useState(initialValue) { state[stateIndex] = state[stateIndex] ?? initialValue; setters[stateIndex] = setters[stateIndex] ?? createSetter(stateIndex); const value = state[stateIndex]; const setValue = setters[stateIndex]; stateIndex++; return [value, setValue]; } // 每次渲染后重置索引 function render() { stateIndex = 0; // ... React 的渲染逻辑 }
这份指南涵盖了 React 面试的核心知识体系。理解这些概念并能结合代码示例阐述,你将能应对绝大多数 React 相关的面试问题。