React
React 和 Vue 有什么区别?
- 模板 vs JSX:Vue 使用模板语法,React 使用 JSX,逻辑和视图都在 JS 里
- 更新机制:Vue 基于响应式系统自动追踪依赖,细粒度更新;React 需要 setState 主动触发,配合 memo/useMemo 做优化
- 数据流:两者都是单向数据流,Vue 还有 v-model 等语法糖
- 生态:Vue 上手快、官方配套全;React 生态更灵活庞大,更偏函数式
- 学习曲线:Vue 平缓,React 需要理解 JSX、Hooks、渲染机制等
React 为什么使用 JSX?
- 声明式描述 UI,结构直观,接近 HTML
- 与 JS 无缝结合,逻辑和视图写在一起,方便表达条件、循环
- 最终由 Babel 编译成 React.createElement() 调用
JSX 是什么?
- JSX 是 JavaScript 的语法扩展,可以在 JS 里写类似 HTML 的结构
- 它不是模板引擎,编译后就是普通的 JS 函数调用
JSX 中为什么只能有一个根节点?
- 因为 JSX 最终编译成 React.createElement() 调用,一个函数只能返回一个值
- 需要多个节点时用 Fragment(
<></>)包裹,它不会产生额外 DOM
JSX 中 class 为什么写成 className?
- 因为 class 是 JavaScript 的保留字,JSX 本质是 JS,不能直接用
- 同理,for 要写成 htmlFor
JSX 中如何绑定事件?
- 用驼峰命名 + 函数引用,如
onClick={handleClick} - 不是字符串,而是传函数,需要传参时用箭头函数包一层
React 中事件和原生 DOM 事件有什么区别?
- React 事件是合成事件(SyntheticEvent),统一了浏览器差异
- 通过事件委托绑定到根容器上(React 17+ 挂在 root 上),不是直接绑在元素上
- 命名是驼峰(onClick),原生是小写(onclick)
- 合成事件对象是池化的,异步访问属性需要注意
React 为什么需要 key?
- 帮助 React 在 diff 时识别列表中的每个节点,判断复用、更新还是删除
- 没有 key 时列表重排只能靠顺序对比,效率低且容易渲染错乱
key 为什么不能使用 index?
- 列表插入、删除、排序后 index 会变,React 会复用错误的节点
- 导致组件状态错乱、DOM 复用错误,比如输入框内容串位
- 应该使用稳定且唯一的 id
什么是受控组件?
- 表单元素的 value 由 React state 控制,通过 onChange 更新 state
- 数据单向流入,值始终和 state 一致,便于校验和处理
什么是非受控组件?
- 表单值由 DOM 自己维护,需要时通过 ref 获取
- 代码简单,但值不在 React 掌控中,需要主动读取
React 中如何获取 DOM?
- 函数组件用 useRef:
const inputRef = useRef(null),<input ref={inputRef} /> - 类组件用 React.createRef()
- 给子组件传 ref 需要 forwardRef 包裹,配合 useImperativeHandle 暴露方法
函数组件和类组件有什么区别?
- 类组件有 this、生命周期、state,写法冗长
- 函数组件更简洁,配合 Hooks 同样能管理 state 和副作用
- React 官方推荐函数组件 + Hooks
什么是 Virtual DOM?
- 用 JS 对象模拟真实 DOM 结构,描述 UI 状态
- 更新时先在 Virtual DOM 上做 diff,算出最小变更,再批量更新真实 DOM
React 为什么需要 Virtual DOM?
- 直接频繁操作真实 DOM 会触发大量重排重绘,性能差
- Virtual DOM 通过 diff 减少真实 DOM 操作次数,提高渲染性能
- 不依赖浏览器,可以跨平台(React Native)
- 让开发者声明式编程,不用关心 DOM 细节
React 的 Virtual DOM 和 Vue 的 Virtual DOM 有什么区别?
- Vue 的模板是编译时的,可以做静态分析优化(静态节点提升、PatchFlags 标记)
- React 的 JSX 偏运行时,diff 时需要更完整地对比
- Vue 的 diff 有双端对比优化,React 的 Fiber 支持可中断渲染和优先级调度
React 是如何进行 Diff 的?
- 同层对比:只比较同一层的节点,不跨层移动
- 类型不同直接替换整棵子树
- 类型相同则复用 DOM,只更新属性
- 列表通过 key 识别复用节点
React 为什么不能直接操作 DOM?
- 直接操作会破坏 React 对 DOM 的掌控,导致虚拟 DOM 和真实 DOM 状态不一致
- React 是声明式框架,期望开发者只描述 UI,由 React 负责更新
- 破坏 React 的优化机制(复用、批量更新等)
React 的 Fiber 是什么?
- React 16 引入的新的协调引擎,核心是"可中断的渲染"
- 把渲染拆成一个个 fiber 任务单元,支持暂停、恢复、按优先级调度
- 解决旧版递归渲染阻塞主线程的问题,配合时间切片让出主线程,保证交互流畅
React 的 Render 阶段和 Commit 阶段是什么?
- Render 阶段:调用组件函数、构建/更新 Fiber 树,计算变化,这个阶段可以中断
- Commit 阶段:把变化提交到真实 DOM,执行副作用(useEffect、生命周期),不可中断
- 对应 current 树和 workInProgress 树双缓冲机制
Props 是什么?
- 父组件传给子组件的参数,用于配置子组件
- 只读,形成单向数据流
State 是什么?
- 组件内部管理的状态,通过 setState 更新
- state 变化会触发组件重新渲染
Props 和 State 有什么区别?
- props 是外部传入的,state 是组件自己维护的
- props 只读不能改,state 通过 setState 修改
- 两者变化都会触发重新渲染
Props 可以修改吗?
- 不可以,props 是只读的
- 子组件需要修改时,应该调用父组件传入的回调,由父组件修改,或把状态提升
State 为什么不能直接修改?
- 直接修改不会触发重新渲染,React 感知不到变化
- setState 会触发渲染调度,并且是不可变更新,方便 diff 对比和调试
什么是 Hooks?
- React 16.8 引入的一组函数,让函数组件也能使用 state、生命周期等能力
- 常用:useState、useEffect、useRef、useMemo、useCallback、useContext、useReducer
为什么 React 要引入 Hooks?
- 解决类组件逻辑复用难的问题(HOC、render props 容易嵌套地狱)
- 状态逻辑可以抽成自定义 Hook 复用
- 函数组件更简洁,没有 this 绑定问题
- 把相关副作用集中管理,替代零散的生命周期
组件通信
- 父传子:props
- 子传父:父组件传回调函数,子组件调用并传参
- 兄弟组件:状态提升到共同的父组件
- 跨层级:Context
- 全局状态:Redux、Zustand 等状态管理库
- 父组件调用子组件方法:forwardRef + useImperativeHandle
React 为什么会重新渲染?
- setState 更新 state
- 父组件重新渲染,默认子组件跟着重新渲染
- props 引用变化、context 值变化
什么情况下组件会重新渲染?
- 自身 state 变化
- 父组件传入的 props 变化
- 订阅的 context 变化
- 父组件重新渲染(未用 memo 时子组件默认跟随)
React 如何避免不必要的重新渲染?
- React.memo 包裹组件,props 没变化时跳过渲染
- useMemo 缓存计算结果,useCallback 缓存函数引用,保证 props 稳定
- 状态拆分下沉,避免一个大的 state 让整棵树都渲染
- 把频繁变化的部分隔离成独立组件
一个组件为什么会重新渲染?
- 本质就三类:state 变了、props 变了(引用变化)、context 变了
- 注意父组件渲染时,子组件默认也会渲染,除非用 memo 阻断
如何优化一个频繁重新渲染的组件?
- React.memo 包裹 + useCallback/useMemo 稳定 props
- 拆分组件,让状态只影响需要的部分
- 大量数据用虚拟列表,只渲染可视区
- 合并多次 setState,减少渲染次数
一个页面有 10000 条数据,React 如何优化?
- 用虚拟列表(react-window / react-virtualized),只渲染可视区域
- 或分页、懒加载
- 每条数据是纯展示组件,用 React.memo 避免无关更新
- 不要一次性渲染大量 DOM
React 中如何实现权限路由?
- 封装一个路由守卫组件,根据路由配置里的 roles 和当前用户角色判断
- 有权限则渲染目标组件,无权限跳转 403 或登录页
- 配合路由配置遍历生成 Routes
React 中如何实现登录状态持久化?
- 登录成功后把 token 存 localStorage / sessionStorage(或 cookie)
- 用户信息放在全局状态(Redux / Zustand)
- 应用启动时从 storage 恢复,请求拦截器统一带上 Authorization
- 刷新页面时根据 token 重新拉取用户信息
React 中如何实现 Token 自动刷新?
- 请求拦截器遇到 401,用 refresh_token 调刷新接口
- 刷新成功拿到新 token,更新存储并重放原请求
- 用一个"单例 Promise + 等待队列"保证并发请求只刷新一次
React 中如何处理接口竞态问题?
- 用 AbortController / axios cancelToken 取消过期请求
- 或者记录最新请求序号,响应返回时判断是否还是最新,不是就丢弃
- 组件卸载时清理,避免 setState 警告
React 中如何取消请求?
- fetch 用 AbortController:
abortController.abort() - axios 用 AbortSignal 或 CancelToken
- 在 useEffect 的清理函数里调用 abort,组件卸载自动取消
React 中如何实现全局 Loading?
- 全局状态维护一个请求计数:发起 +1,结束 -1,为 0 时隐藏
- 在 axios 请求/响应拦截器里统一增减
- 或直接调用 antd 的 message/Spin 全局实例
React 中如何实现错误处理?
- 请求层:axios 拦截器统一处理错误码和提示
- 渲染层:Error Boundary 捕获渲染错误,展示降级 UI
- 全局:监听 unhandledrejection 兜底异步异常
- 上报:错误统一记录并上报监控平台
React 中如何实现 Error Boundary?
- 类组件实现 getDerivedStateFromError 和 componentDidCatch
- 捕获子树渲染错误,渲染备用 UI 并上报
- 注意:只能捕获渲染期和生命周期错误,事件处理、异步错误捕获不到,需要配合全局兜底
Tree Shaking 是什么?
- 基于 ES Module 的静态分析,打包时移除未被引用的代码
- 依赖 import/export 的静态性,CommonJS 无法摇树
- 需要注意包的 sideEffects 字段,避免误删有副作用的代码
Babel 是什么?
- JavaScript 编译器,把新语法、JSX、TypeScript 转成兼容旧环境的代码
- 核心流程:parse 解析 -> transform 转换 -> generate 生成
- 通过插件和预设(preset-env、preset-react)扩展能力
ESLint 是什么?
- 代码静态检查工具,发现语法错误、风格问题和潜在 bug
- 支持自定义规则和插件(如 eslint-plugin-react)
- 常配合 Prettier 一起使用,一个管规则一个管格式
npm、pnpm、yarn 有什么区别?
- npm:默认包管理器,node_modules 平铺,历史问题多
- yarn:解决早期 npm 安装慢、依赖版本不统一的问题,有 yarn.lock
- pnpm:通过硬链接 + 内容寻址存储,节省磁盘、安装快,还能避免幽灵依赖