Skip to content

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 // 下一个有副作用的节点
}

工作流程:

  1. Render 阶段(可中断): 构建 Fiber 树,标记副作用
  2. 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 使用规则:

  1. 只在最顶层使用 Hook,不要在循环、条件或嵌套函数中调用
  2. 只在 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 的区别是什么?

答案要点:

特性useEffectuseLayoutEffect
执行时机浏览器绘制后浏览器绘制前
阻塞渲染
使用场景大多数副作用需要同步执行避免闪烁
性能影响大(可能阻塞渲染)

代码示例:

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 渲染控制

题目:如何避免不必要的重渲染?

答案要点:

重渲染原因:

  1. 父组件渲染导致子组件渲染
  2. state 变化
  3. props 变化
  4. 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 算法的核心策略:

  1. 只比较同层级节点 - 不会跨层级比较
  2. 使用 key 优化列表比较 - O(n) 复杂度
  3. 单端比较 - 从一端开始比较

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 事件特点:

  1. 合成事件(SyntheticEvent) - 跨浏览器兼容
  2. 事件委托 - 所有事件委托到 document(React 17+ 委托到 root)
  3. 事件池(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 优势:

  1. SEO 友好 - 搜索引擎可以抓取完整内容
  2. 首屏加载快 - 用户更快看到内容
  3. 性能优化 - 减少客户端计算

实现方式:

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客户端渲染后台管理、应用