React 框架
1. React Fiber 架构
题目:React 的 Fiber 架构解决了什么问题?它的工作原理是什么?
答案要点:
Fiber 架构解决了 React 在处理大型组件树时,由于同步渲染阻塞主线程导致的掉帧问题。
- 引入了协作式调度,将渲染工作拆分为可中断的微小单元
- 分为 Reconciliation(协调)和 Commit(提交)两个阶段
- 协调阶段是可中断的,通过 requestIdleCallback 寻找执行时机
- 双缓存技术: 在内存中构建 WorkInProgress Tree,完成后一次性切换
Fiber 节点结构:
javascript
{
type: 'div', // 组件类型
key: null, // key
props: {}, // 属性
stateNode: DOMNode, // 真实 DOM 节点
child: Fiber | null, // 第一个子节点
sibling: Fiber | null, // 下一个兄弟节点
return: Fiber | null, // 父节点
alternate: Fiber | null, // 当前树对应的节点(双缓存)
effectTag: number, // 副作用标记
nextEffect: Fiber | null // 下一个有副作用的节点
}工作流程:
- Render 阶段(可中断): 构建 Fiber 树,标记副作用
- Commit 阶段(不可中断): 执行 DOM 操作,执行副作用
常见坑:
- 误认为 Fiber 提高了渲染速度(实际上是提高了响应速度)
- 混淆了 useEffect 和 useLayoutEffect 在 Fiber 周期中的执行时机
追问:
- 为什么 React 放弃了原有的递归 Diff,改用链表结构的 Fiber?
2. React 生命周期
题目:React 类组件和函数组件的生命周期有什么区别?
答案要点:
类组件生命周期:
挂载阶段:
constructor -> getDerivedStateFromProps -> render -> componentDidMount
更新阶段:
getDerivedStateFromProps -> shouldComponentUpdate -> render ->
getSnapshotBeforeUpdate -> componentDidUpdate
卸载阶段:
componentWillUnmount函数组件(Hooks):
javascript
// 相当于 componentDidMount + componentDidUpdate
useEffect(() => {
// 副作用
return () => {
// 清理函数(相当于 componentWillUnmount)
}
}, [deps])
// 相当于 componentDidMount
useEffect(() => {
// 只执行一次
}, [])
// DOM 更新后同步执行
useLayoutEffect(() => {
// 同步执行,避免闪烁
}, [])3. React Hooks
题目:React Hooks 的使用限制及原因是什么?
答案要点:
Hooks 使用规则:
- 只在最顶层使用 Hook,不要在循环、条件或嵌套函数中调用
- 只在 React 函数中调用 Hook,不要在普通 JavaScript 函数中调用
原因:
React 通过链表结构来存储 Hooks 的状态,依靠调用顺序来对应状态。
javascript
// React 内部实现简化的 Hooks 管理
const HooksDispatcher = {
useState: (initialState) => {
const hook = workInProgressHook
workInProgressHook = workInProgressHook.next
return [hook.memoizedState, hook.dispatch]
},
}如果条件调用导致顺序不一致,状态就会错乱:
javascript
// ❌ 错误示例
function Component() {
if (condition) {
const [state, setState] = useState(0) // 条件调用,可能导致 bug
}
}题目:useEffect 和 useLayoutEffect 的区别是什么?
答案要点:
| 特性 | useEffect | useLayoutEffect |
|---|---|---|
| 执行时机 | 浏览器绘制后 | 浏览器绘制前 |
| 阻塞渲染 | 否 | 是 |
| 使用场景 | 大多数副作用 | 需要同步执行避免闪烁 |
| 性能影响 | 小 | 大(可能阻塞渲染) |
代码示例:
javascript
// useEffect - 异步执行,不阻塞渲染
useEffect(() => {
// DOM 已更新并绘制到屏幕
console.log('useEffect')
}, [])
// useLayoutEffect - 同步执行,在绘制前
useLayoutEffect(() => {
// DOM 已更新但还未绘制
// 可以在此测量 DOM 并同步修改,避免视觉闪烁
const { width } = ref.current.getBoundingClientRect()
setWidth(width)
}, [])4. React 状态管理
题目:React 有哪些状态管理方案?如何选择?
答案要点:
| 方案 | 适用场景 | 特点 |
|---|---|---|
| useState/useReducer | 组件级状态 | 简单,无需额外库 |
| Context API | 跨组件共享 | 适合低频更新 |
| Redux | 大型应用 | 单一数据源,可预测 |
| Zustand | 中小型应用 | 简洁,无样板代码 |
| Jotai/Recoil | 原子化状态 | 细粒度更新 |
| React Query | 服务端状态 | 缓存、同步、去重 |
Context 使用示例:
javascript
// 创建 Context
const ThemeContext = createContext()
// Provider
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>
}
// 使用
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext)
return <button onClick={() => setTheme('dark')}>{theme}</button>
}5. React 性能优化
题目:React 有哪些性能优化手段?
答案要点:
| 优化手段 | 说明 | 使用场景 |
|---|---|---|
| React.memo | 组件记忆化 | 纯展示组件 |
| useMemo | 缓存计算结果 | 复杂计算 |
| useCallback | 缓存函数引用 | 子组件依赖函数 |
| shouldComponentUpdate | 控制更新 | 类组件 |
| PureComponent | 浅比较 props/state | 类组件 |
| 虚拟列表 | 只渲染可视区域 | 长列表 |
| 代码分割 | 懒加载组件 | 大型应用 |
| useTransition | 非紧急更新 | React 18 |
| useDeferredValue | 延迟更新值 | React 18 |
代码示例:
javascript
// React.memo - 组件记忆化
const MemoizedComponent = React.memo(
({ data }) => {
return <div>{data.name}</div>
},
(prevProps, nextProps) => {
// 自定义比较函数
return prevProps.data.id === nextProps.data.id
},
)
// useMemo - 缓存计算
const expensiveValue = useMemo(() => {
return data.filter((item) => item.active).sort((a, b) => b.score - a.score)
}, [data])
// useCallback - 缓存函数
const handleClick = useCallback((id) => {
console.log(id)
}, []) // 空依赖,函数引用不变常见坑:
- 过度使用 useMemo/useCallback,反而增加开销
- 比较函数写错导致不必要的渲染
6. React 渲染控制
题目:如何避免不必要的重渲染?
答案要点:
重渲染原因:
- 父组件渲染导致子组件渲染
- state 变化
- props 变化
- context 变化
优化策略:
javascript
// 1. 使用 React.memo
const Child = React.memo(({ data }) => {
return <div>{data}</div>
})
// 2. 使用 useMemo 稳定引用
const stableData = useMemo(() => data, [data.id])
// 3. 使用 useCallback 稳定函数
const stableCallback = useCallback(() => {
doSomething(data.id)
}, [data.id])
// 4. 状态提升或下降,避免不必要的影响范围
// 5. 使用 key 强制重新挂载
<Component key={uniqueId} />7. React 18 新特性
题目:React 18 有哪些重要的新特性?
答案要点:
- 并发渲染(Concurrent Rendering) - 可中断的渲染
- 自动批处理(Automatic Batching) - 自动合并状态更新
- Suspense 改进 - 支持数据获取
- useTransition - 标记非紧急更新
- useDeferredValue - 延迟更新值
- 新的 Root API -
createRoot替代ReactDOM.render
代码示例:
javascript
// React 18 Root API
import { createRoot } from 'react-dom/client'
const root = createRoot(document.getElementById('root'))
root.render(<App />)
// useTransition - 非紧急更新
function TabContainer() {
const [isPending, startTransition] = useTransition()
const [tab, setTab] = useState('home')
const selectTab = (nextTab) => {
startTransition(() => {
setTab(nextTab) // 非紧急更新,可中断
})
}
return (
<>
{isPending && <Spinner />}
<TabButton onClick={() => selectTab('about')}>About</TabButton>
</>
)
}
// useDeferredValue - 延迟值
function SearchResults({ query }) {
const deferredQuery = useDeferredValue(query)
// deferredQuery 会在紧急更新完成后才更新
return <Results query={deferredQuery} />
}8. React Diff 算法
题目:详细说明 React 的 Diff 算法
答案要点:
React Diff 算法的核心策略:
- 只比较同层级节点 - 不会跨层级比较
- 使用 key 优化列表比较 - O(n) 复杂度
- 单端比较 - 从一端开始比较
Diff 过程:
javascript
// 单链表 Diff 简化逻辑
function reconcileChildren(current, workInProgress, nextChildren) {
let oldFiber = current.child
let newIdx = 0
// 第一轮:按顺序比较
for (; oldFiber !== null && newIdx < nextChildren.length; newIdx++) {
if (oldFiber.key === nextChildren[newIdx].key) {
// key 相同,复用节点
updateFiber(oldFiber, nextChildren[newIdx])
} else {
// key 不同,标记删除
deleteFiber(oldFiber)
}
oldFiber = oldFiber.sibling
}
// 第二轮:处理新增节点
for (; newIdx < nextChildren.length; newIdx++) {
createFiber(nextChildren[newIdx])
}
}Key 的作用:
- 帮助 React 识别哪些元素改变了
- 减少不必要的 DOM 操作
- 保持组件状态
为什么不要用 index 作为 key?
javascript
// ❌ 错误:使用 index 作为 key
{
items.map((item, index) => <li key={index}>{item.name}</li>)
}
// ✅ 正确:使用唯一标识作为 key
{
items.map((item) => <li key={item.id}>{item.name}</li>)
}9. React 事件机制
题目:React 事件机制有什么特点?
答案要点:
React 事件特点:
- 合成事件(SyntheticEvent) - 跨浏览器兼容
- 事件委托 - 所有事件委托到 document(React 17+ 委托到 root)
- 事件池(React 17 之前) - 事件对象复用
与原生事件的区别:
| 特性 | React 事件 | 原生事件 |
|---|---|---|
| 命名 | 驼峰 onClick | 小写 onclick |
| 阻止默认 | e.preventDefault() | return false |
| 事件对象 | SyntheticEvent | 原生 Event |
| 绑定方式 | 属性绑定 | addEventListener |
代码示例:
javascript
// React 事件
function Component() {
const handleClick = (e) => {
e.preventDefault() // 阻止默认行为
e.stopPropagation() // 阻止冒泡
console.log(e.nativeEvent) // 访问原生事件
}
return <button onClick={handleClick}>点击</button>
}10. React 服务端渲染
题目:React 服务端渲染(SSR)有什么优势?如何实现?
答案要点:
SSR 优势:
- SEO 友好 - 搜索引擎可以抓取完整内容
- 首屏加载快 - 用户更快看到内容
- 性能优化 - 减少客户端计算
实现方式:
javascript
// Next.js 自动 SSR
// pages/index.js
export default function Home({ data }) {
return <div>{data.title}</div>
}
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data')
const data = await res.json()
return { props: { data } }
}
// React 18 流式 SSR
import { renderToPipeableStream } from 'react-dom/server'
const stream = renderToPipeableStream(<App />, {
onShellReady() {
response.statusCode = 200
stream.pipe(response)
},
})SSR vs SSG vs CSR:
| 方式 | 渲染时机 | 适用场景 |
|---|---|---|
| SSR | 请求时渲染 | 动态内容、SEO |
| SSG | 构建时渲染 | 静态内容、博客 |
| CSR | 客户端渲染 | 后台管理、应用 |
