React面试题
本文最后更新于 2025年11月13日 凌晨
Component、Hook、Utils
| 类型 | 封装 | 渲染 UI | 依赖生命周期 | 使用场景 |
|---|---|---|---|---|
| react 组件 | UI 结构和逻辑 | ✅ | ✅ | 页面、模块、UI 片段 |
| 自定义 hook | 可复用的逻辑状态 | ❌ | ✅ | 状态逻辑、数据请求、订阅等 |
| utils 函数 | 纯逻辑或通用计算 | ❌ | ❌ | 格式化、计算、API 调用等 |
react 组件
- 特征:能返回 UI(JSX)
- 用途:构建界面(View 层)
- 特点:
- 可以有 state
- 可以有 props
- 可以渲染 HTML 结构
- 可以使用 Hook(包括自定义 Hook)
- 示例:
1
2
3
4
5
6
7
8function UserCard({ user }) {
return (
<div className="card">
<img src={user.avatar} />
<p>{user.name}</p>
</div>
)
}
✅ 用来展示界面(UI)
⚠️ 不推荐在组件中堆太多逻辑,应交给 hook 或 utils
自定义 hook
- 特征:以 use 开头、封装状态逻辑,可在多个组件中共享
- 用途:封装“与 React 生命周期有关”的逻辑,比如:
- 状态管理(useState、useReducer)
- 副作用(useEffect)
- 数据请求(fetch、WebSocket)
- DOM 监听(scroll、resize)
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13import { useState, useEffect } from 'react'
function useUserInfo() {
const [user, setUser] = useState(null)
useEffect(() => {
fetch('/api/user').then(res => res.json()).then(setUser)
}, [])
return user
}
export default useUserInfo
✅ 可以被多个组件复用逻辑
❌ 不渲染 UI,自身返回的是“数据和行为”
utils 函数函数
- 特征:纯逻辑、无状态、无 React 依赖
- 用途:做纯计算、格式化、工具类操作
- 场景:
- 时间格式化(
formatDate()) - 金额处理(
formatPrice()) - 深拷贝、节流防抖
- 网络请求封装(如
request())
- 时间格式化(
- 示例:
1
2
3export function formatPrice(price: number) {
return '¥' + price.toFixed(2)
}
✅ 任何 JS 环境都能用
✅ 不依赖 React
✅ 可在 Node、Vue 等项目中重用
三者关系
1 | |
- utils:通用工具逻辑
- hook:封装 React 状态逻辑
- component:渲染 UI
React 和 Vue 的区别
React 和 Vue 都是流行的前端框架,但有一些核心区别:
- React 更偏向函数式编程和单向数据流,本质是 UI 库,需要依赖社区生态来解决路由、状态管理等问题;而 Vue 是渐进式框架,开箱即用,支持双向绑定,学习曲线更平滑。
- 在视图层上,React 使用 JSX,逻辑和视图融合度更高,更灵活;Vue 使用模板和指令,写法更直观。
- 响应式上,React 借助 Fiber 架构和 Virtual DOM 来驱动更新,而 Vue 2 用 defineProperty,Vue 3 用 Proxy,响应式能力更强。
总体来看,React 更适合大型复杂应用和跨端场景,Vue 更适合快速开发和中小型项目。
为什么要用 Hook
- 解决逻辑复用难的问题。类组件里想复用逻辑,只能用高阶组件 (HOC) 或 Render Props,会导致组件嵌套地狱。Hooks 允许把状态逻辑提取到自定义 Hook 中,实现逻辑共享
- 解决类组件复杂度高的问题。一个功能的逻辑可能分散在多个生命周期函数:订阅在
componentDidMount、更新在componentDidUpdate、清理在componentWillUnmount。Hooks 用useEffect就能把逻辑集中到一起,更符合“关注点分离”的设计 - 解决 this 问题。类组件里 this 经常出错,需要手动绑定。函数组件没有 this 彻底解决此困扰
- 更好支持函数式编程和 Fiber 架构。函数组件 + Hook 更契合 React 声明式和可中断渲染。在执行过程能暂停、恢复甚至丢弃函数组件的执行,而类组件里状态绑定在实例上不好做
为什么只能在函数内部最外层调用 Hook
因为 React 使用单向链表来存储每个组件调用的 Hook 状态,每个 Hook 节点都存储着它的状态(如 state、effect 函数、依赖项等)。React 通过调用顺序来判断哪个 Hook 对应哪个状态。
函数组件的每次渲染都是完全独立的。也就是说,每个函数中的 state 变量只是一个简单的常量,每次渲染时从钩子中获取到的常量,没有附着数据绑定(除了 useRef.current 返回是引用)。
所以如果把 Hook 放在条件语句/循环/子函数里,就会破坏调用顺序,React 无法正确找到对应的 Hook 状态。
为什么只能在 React 函数组件或自定义 Hook 中用 Hook
因为 Hooks 必须被关联到一个具体的 React 组件实例上。React 内部有一个当前正在渲染组件的指向(可以理解为一个全局的“光标”)。当调用 Hook 时 React 会检查这个“光标”,知道这个状态是属于哪个组件的,然后把它挂载到对应的 Fiber 节点(React 内部表示组件的结构)上。
React 性能优化
- React.memo、shouldCompountUpdata、useMemo、useCallBack
- index、key
- render、useTransition、useDeferredValue
- React.lanzy、Suspance
- useContext
- useEffect、return
- useRuducer
来源:React 性能优化十大总结、React 性能优化深度指南:从基础到高级技巧
React18 新特性
- render、useTransition、useDeferredValue
- setSate 批处理、flushSync
React diff 算法过程
在 React 中,diff 算法需要与虚拟 DOM 配合才能发挥出真正的效果。React 会使用 diff 算法算出虚拟 DOM 中真正发生变化的部分,并且只会针对该部分进行 DOM 操作,从而避免了对页面进行大面积的更新渲染,减小性能的开销。
React 定义了三大策略,在对比时根据策略只需遍历一次树就可以完成对比,复杂度降到了 O(n)
- tree diff:在两个树对比时,只会比较同一层级的节点,忽略跨层级的操作
- component diff:在对比两个组件时,首先会判断它们两个的类型是否相同,如果不同直接替换整个组件下的所有子节点
- element diff:同一层级的节点使用唯一性的 key 来区分是否需要创建、删除、移动
为何 React JSX 循环需要使用 key
React 通过 key 唯一标识列表中的每个元素。当列表发生变化时,React 会通过 key 快速判断,哪些元素是新增、哪些是移除、哪些是移动,从而提高性能。如果没有 key,React 默认使用数组索引作为标识,会认为所有 key 变化的元素都是新节点,从而进行不必要的重建,而不是高效的移动,导致性能下降和潜在的状态 bug。