1.JSX是什么,它和JS有什么区别?
JSX 是 JavaScript 语法的扩展,它允许编写类似于 HTML 的代码。它可以编译为常规的 JavaScript 函数调用,从而为创建组件标记提供了一种更好的方法。
- jsx是React.createElement(component, props, ...children) 函数的语法糖
- 底层是使用babel-plugin-transform-react-jsx插件 将jsx的语法转化为js对象,判断是否是jsx对象或是否是一个组件,转化为对应的js对象(虚拟dom)
2.简述React的生命周期
简短版本
React生命周期方法是在组件不同阶段执行的特定方法。以下是一些常用的React生命周期方法:
- getDefaultProps:获取实例的默认属性
- getInitialState:获取每个实例的初始化状态
- componentWillMount:组件即将被挂载、渲染到页面上
- render:组件在这里生成虚拟的Dom节点
- componentDidMount:组件真正在被装载之后 运行中的状态
- componentWillReceiveProps:组件将要接受到属性的时候调用
- shouldComponentUpdate:组件接受到新属性或者状态时(可以返回false,接受属性不更新,阻止render调用,后面的函数不会被继续执行了)
- componentWillUpdate:组件即将更新,不能修改属性和状态
- render:组件重新描绘
- componentDidUpdate:组件已经更新
- componentWillUnmount:组件即将销毁
详细版本
React 的生命周期主要分为三个阶段:MOUNTING、RECEIVE_PROPS、UNMOUNTING
-
组件挂载时(组件状态的初始化,读取初始 state 和 props 以及两个生命周期方法,只会在初始化时运行一次)
- componentWillMount 会在 render 之前调用(在此调用 setState,是不会触发 re-render 的,而是会进行 state 的合并。因此此时的 this.state 不是最新的,在 render 中才可以获取更新后的 this.state。)
- componentDidMount 会在 render 之后调用
-
组件更新时(组件的更新过程是指父组件向下传递 props 或者组件自身执行 setState 方法时发生的一系列更新的动作)
-
组件自身的 state 更新,依次执行
- shouldComponentUpdate(会接收需要更新的 props 和 state,让开发者增加必要的判断条件,在其需要的时候更新,不需要的时候不更新。如果返回的是 false,那么组件就不再向下执行生命周期方法。)
- componentWillUpdate
- render(能获取到最新的 this.state)
- componentDidUpdate(能获取到最新的 this.state)
-
父组件更新 props 而更新
- componentWillReceiveProps(在此调用 setState,是不会触发 re-render 的,而是会进行 state 的合并。因此此时的 this.state 不是最新的,在 render 中才可以获取更新后的 this.state。
- shouldComponentUpdate
- componentWillUpdate
- render
- componentDidUpdate
-
-
组件卸载时
- componentWillMount(我们常常会在组件的卸载过程中执行一些清理方法,比如事件回收、清空定时器)
新版的生命周期函数增加了 getDerivedStateFromProps,这个生命周期其实就是将传入的 props 映射到 state 中。在 React 16.4 之后,这个函数每次会在 re-render 之前调用,
getDerivedStateFromProps的作用是
- 无条件的根据 prop 来更新内部 state,也就是只要有传入 prop 值, 就更新 state
- 只有 prop 值和 state 值不同时才更新 state 值。
但是盲目使用这个生命周期会有一些问题
- 直接复制 props 到 state 上
- 如果 props 和 state 不一致就更新 state
3.React事件机制和原生DOM事件流有什么区别
react中的事件是绑定到document上面的,而原生的事件是绑定到dom上面的,因此相对绑定的地方来说,dom上的事件要优先于document上的事件执行
4.Redux工作原理
Redux 是 React 的第三方状态管理库,创建于上下文API存在之前。它基于一个称为存储的状态容器的概念,组件可以从该容器中作为 props 接收数据。更新存储区的唯一方法是向存储区发送一个操作,该操作被传递到一个reducer中。reducer接收操作和当前状态,并返回一个新状态,触发订阅的组件重新渲染。
5.SetState是同步还是异步的,setState做了什么
首先,同步和异步主要取决于它被调用的环境
这里的同步还是异步,指的调用setState方法后,是否能立刻拿到更新后的值
- 如果 setState 在 React 能够控制的范围被调用,它就是异步的。比如合成事件处理函数、生命周期函数 在合成事件和钩子函数中,多次调用setState 修改同一个值,只会取最后一次的执行,前面的会被覆盖
- 如果 setState 在原生 JavaScript 控制的范围被调用,它就是同步的。比如原生事件处理函数、setTimeout、promise的回调函数等。在原生事件和异步中,可以多次调用setState 修改同一个值,每次修改都会生效
6.react中的合成事件和原生事件
react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件
原生事件是指非react合成事件,原生自带的事件监听addEventListener,或者也可以用原生js、jq直接绑定事件的形式都属于原生事件
7.什么是fiber,fiber解决了什么问题
解决react旧版本,更新页面时会出现丢帧卡顿的问题
React旧版本问题
当我们调用setState更新页面的时候,React会遍历应用的所有节点,计算出差异,然后再更新 UI 整个过程是一气呵成,不能被打断的。如果页面元素很多,整个过程执行的时间可能超过 50 毫秒,就容易出现掉帧的现象
新版本解决方案
React Fiber是把一个大任务拆分为了很多个小块任务,一个小块任务的执行必须是一次完成的,不能出现暂停,但是一个小块任务执行完后可以移交控制权给浏览器去响应用户操作
核心是通过 requestIdleCallback ,会在利用浏览器空闲时间会找出所有需要变更的节点
- 阶段一,生成 Fiber 树,得出需要更新的节点信息,这一步是一个渐进的过程,可以被打断
- 阶段二,将需要更新的节点一次性批量更新,这个过程不能被打断
总结回答:
Fiber 是 React 16 引入的新的协调算法。它通过将渲染工作拆分成可中断的小单元,解决了大组件树渲染时阻塞主线程的问题。核心是基于链表的 Fiber 节点结构和双缓存机制,实现了时间切片、优先级调度和增量渲染。
① 数据结构:从递归的虚拟DOM树改为基于链表的 Fiber 节点
② 调度机制:引入 requestIdleCallback 实现时间切片
③ 双缓存:workInProgress 树和 current 树交替更新
④ 优先级:为不同交互设置更新优先级(如用户输入 > 数据更新)
协调阶段(Reconcile)是可中断的,React 会在帧的空闲时间执行。而提交阶段(Commit)是同步的,确保DOM更新一次性完成。Fiber 节点保存了组件状态、DOM引用和副作用标记,通过 child、sibling、return 三个指针实现遍历。
react协调算法是什么实现的
React协调算法是比较新旧虚拟DOM树差异的机制,Fiber是它的重构版,从不可中断的递归变成可中断的链表遍历,为实现并发渲染提供基础。
Fiber带来的三大能力
1. 可中断渲染
2. 优先级调度
- 用户交互(高优先级)可中断数据渲染(低优先级)
- 高优先级更新能"插队"执行
3. 并发渲染(React 18)
- 多个更新可同时进行
- 渲染开始后仍可接收新更新
什么是Fiber 架构,空闲渲染有了解吗?
Fiber架构包含三个核心改进:
- 数据结构:组件对应Fiber节点,形成child/sibling/return链表
- 可中断执行:从递归改为循环,支持暂停恢复
- 优先级调度:不同更新分配不同优先级
空闲渲染基于时间切片技术:
- 将任务拆成5ms小片
- 每片执行完检查是否还有空闲时间
- 没有就暂停,等下次空闲继续
- 高优先级任务可中断低优先级
这解决了传统React递归渲染阻塞主线程的问题,为并发模式打下基础。
8.react中使用了Fiber,为什么vue没有用Fiber?
原因是二者的更新机制不一样
- Vue 是基于 template 和 watcher 的组件级更新,把每个更新任务分割得足够小,不需要使用到 Fiber 架构,将任务进行更细粒度的拆分
- React 是不管在哪里调用 setState,都是从根节点开始更新的,更新任务还是很大,需要使用到 Fiber 将大任务分割为多个小任务,可以中断和恢复,不阻塞主进程执行高优先级的任务,如果不用Fiber,会出现老版本卡顿的问题
9.react中props和state有什么区别
props 是传递给组件的(类似于函数的形参),而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)
props 是不可修改的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。 由于 props 是传入的,并且它们不能更改,因此我们可以将任何仅使用 props 的 React 组件视为 pureComponent,也就是说,在相同的输入下,它将始终呈现相同的输出。 state 是在组件中创建的,一般在 constructor中初始化 state state 是多变的、可以修改,每次setState都异步更新的。
10.为什么虚拟 dom 会提高性能?
虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。 具体实现步骤如下:
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。
11.vue和react的区别
1)设计理念不同
- react整体上是函数式编程思想,组件使用jsx语法,all in js,将html与css全都融入javaScript中,jsx语法相对来说更加灵活
- vue的整体思想,是拥抱经典的html(结构)+css(表现)+js(行为)的形式,使用template模板,并提供指令供开发者使用,如v-if、v-show、v-for等,开发时有结构、表现、行为分离的感觉
2)数据是否可变
- vue的思想是响应式的,通过Object.defineproperty或proxy代理实现数据监听,每一个属性添加一个dep对象(用来存储对应的watcher),当属性变化的时候,通知对应的watcher发生改变
- react推崇的是数据不可变,react使用的是浅比较,如果对象和数据的引用地址没有变,react认为该对象没有变化,所以react变化时一般都是新创建一个对象
3)更新渲染方式不同
- 当组件的状态发生变化时,vue是响应式,通过对应的watcher自动找到对应的组件重新渲染
- react需要更新组件时,会重新走渲染的流程,通过从根节点开始遍历,dom diff找到需要变更的节点,更新任务还是很大,需要使用到 Fiber,将大任务分割为多个小任务,可以中断和恢复,不阻塞主进程执行高优先级的任务
4)各自的优势不同
- vue的优势包括:框架内部封装的多,更容易上手,简单的语法及项目创建, 更快的渲染速度和更小的体积
- react的优势包括: react更灵活,更接近原生的js、可操控性强,对于能力强的人,更容易造出更个性化的项目
12.react Hooks
可以在函数式组件中,获取state、refs、生命周期钩子等其他特性
Hook 核心原理
- 存储结构:Hooks数据存储在Fiber节点的
memoizedState属性中,通过单向链表管理 - 执行顺序依赖:Hooks调用顺序在每次渲染中必须严格一致(链表顺序不可变)
- 闭包陷阱:每个Hooks闭包捕获当次渲染的props/state快照
Hook 使用规则
- 只在最顶层使用 Hook,Hooks底层使用链表存储数据,按照定义的useState顺序存储对应的数据,不要在循环、条件或嵌套函数中调用Hook,否则 Hooks的顺序会错乱
- Hook 命名规则:自定义 Hook 必须以 “use” 开头,如useFriendStatus
- Hooks 只能在函数组件中使用,不能在类组件中使用。这是因为 Hooks 是基于函数组件的思想设计的,它利用了函数组件的闭包特性来存储和更新状态
- 在两个组件中使用相同的 Hook 不会共享 state,每次使用自定义 Hook 时,其中的所有state和副作用都是完全隔离的
13.为什么vue和react都选择了Hooks
1. 更好的状态复用
- 对于vue2来说,使用的是mixin进行混入,会造成方法与属性的难以追溯。
- 随着项目的复杂,文件的增多,经常会出现不知道某个变量在哪里引入的,几个文件间来回翻找,
- 同时还会出现同名变量,相互覆盖的情况……😥
2. 更好的代码组织
- vue2的属性是放到data中,方法定义在methods中,修改某一块的业务逻辑时,经常会出现代码间来回跳转的情况,增加开发人员的心智负担
- 使用Hooks后,可以将相同的业务逻辑放到一起,高效而清晰地组织代码
3. 告别this
this有多种绑定方式,存在显示绑定、隐式绑定、默认绑定等多种玩法,里边的坑不是一般的多
vue3的setup函数中不能使用this,不能用挺好,直接避免使用this可能会造成错误的
14.为什么react推行函数式组件
- 函数组件不需要声明类,可以避免大量的譬如extends或者constructor这样的代码
- 函数组件不需要处理 this 指向的问题
- 函数组件更贴近于函数式编程,更加贴近react的原则。使用函数式编程,灵活度更高,更好的代码复用
- 随着Hooks功能的强大,更推动了函数式组件 + Hooks 这对组合的发展
15.useMemo和useCallback的作用与区别
useCallback 返回一个函数,只有在依赖项发生变化的时候才会更新(返回一个新的函数),多用于生成一个防抖函数
注意:组件每次更新时,所有方法都会重新创建,这样之前写的防抖函数就会失效,需要使用useCallback包裹
useMemo 只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值, 类似于vue中的computed 计算属性
16.useEffect的用法与作用
- useEffect的用法:需要传入两个参数:副作用函数和依赖数组
- 作用:
处理副作用操作:在副作用函数内部,可以执行需要在组件渲染时或更新时执行的副作用操作,例如订阅数据、操作DOM、发送请求等控制组件生命周期:useEffect函数可以模拟类组件的生命周期方法(如componentDidMount、componentDidUpdate和componentWillUnmount)的行为。
17.使用setCount修改数据后,到页面重新渲染,整个流程是怎么样的
初始化状态:使用useState定义一个状态变量和对应的更新函数。例如:const [count, setCount] = useState(0);
执行 setCount(count + 1) 后,到页面重新渲染的整个流程如下:
- 当你调用 setCount(count + 1),React会记录这个更新操作,并将新的值(count + 1)存储在内部。
- React会将组件标记为“脏”(dirty),表示需要重新渲染。
- 组件函数将被再次执行,这一次执行过程中会重新计算组件的UI。在这个过程中,count的值被更新为新的值(count + 1),这将反映在生成的UI中。
- React会将前后两次渲染生成的虚拟DOM树进行比较,找出变化的部分。在这种情况下,count的值发生了变化,因此会更新与count相关的部分。
- 只有变化的部分会被重新渲染,这样可以提高性能。React会将变化的部分转换为实际的DOM操作,更新到页面上。
- 页面更新完成后,用户将看到更新后的页面,其中包含了新的count值的呈现。 总结起来,执行 setCount(count + 1) 后,React会在后续的重新渲染过程中更新组件的UI,使其反映新的状态值。这样,页面就会显示更新后的数据。
讲一下 react render的全流程?
React render 是从组件更新到屏幕显示的全过程,分为 渲染(render)和提交(commit) 两大阶段,中间有 协调(reconciliation) 过程来高效更新 DOM。
1. 触发阶段
- 原因:
setState()、useState()、父组件重渲染、forceUpdate()、Context 变更 - 结果:React 标记需要更新的组件,开始调度
2. 渲染阶段(可中断的虚拟 DOM 计算)
- 纯计算:没有实际 DOM 操作
- 可中断:React 18+ 可暂停渲染处理高优先级任务
- 生成更新计划:知道哪些 DOM 需要改,但还没改
3. 提交阶段(不可中断的实际 DOM 更新)
4. 浏览器渲染
- 浏览器流程:样式计算 → 布局 → 绘制 → 合成
- React 完成:组件显示在屏幕上
17.React hooks解决了什么问题? 函数组件与类组件的区别
函数组件:
- 没有组件实例
- 没有生命周期
- 没有state和setState,只能接受props
- 函数组件是一个纯函数,执行完即销毁,无法储存state
- 需要state hook,即把state功能’钩’到纯函数中
class组件
- 大型组件很难拆分和重构,很难测试(即class不易拆分)
- 相同业务逻辑,分散到各个方法中,逻辑混乱
- 复用逻辑变得复杂,如Mixins、HOC、Render Props
为什么会有react hooks,它解决了什么问题?
- 解决了使用class组件带了一个this指向问题,逻辑与视图强绑定,强耦合的问题
- 解决了逻辑组件复用,一次编写,不同业务组件使用
- hook带来了一个最大的好处就是,原来的那种class组件的编写方式,面对对象编程转化成了函数式编程
18.React组件传值有哪些方式
父传子:props 子传父:通过在父组件引入的子组件中传递一个函数并传参数,子组件去触发这个函数更改参数完成数据更新
跨多层组件传值:通过context api完成
19.React diff 原理
- 把树形结构按照层级分解,只比较同级元素。
- 列表结构的每个单元添加唯一的 key 属性,方便比较。
- React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)
- 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty 到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.
- 选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。
20.hooks
hooks基础
useState、useEffect、useRef、useContext、useReducer、useMemo、useCallback
react hooks如何模拟组件生命周期?
- 模拟componentDidMount useEffect依赖为空
- 模拟componentDidUpdate useEffect无依赖,或者依赖[a,b]
- 模拟componentWillUnMount useEffect中retrun一个函数
为什么react useState 要用数组解构,而不是函数?
React选择数组解构,主要因为:
- 命名自由:数组解构允许自定义变量名,多个useState调用不会冲突
- 简洁直观:
[state, setState]直接表达配对关系,代码更干净 - 符合习惯:与React Hooks的顺序调用特性自然契合”
react hooks使用规范
- 只能用于顶层代码,不能在循环、判断中使用hooks
- eslint插件eslint-plugin-react-hooks可以规范
hook调用顺序必须保持一致
- 无论是render还是re-render,hooks调用顺序必须一致
- 如果hooks出现在循环、判断里,则无法保证顺序一致
- hooks严重依赖于调用顺序
react hooks性能优化
- 用useMemo缓存数据
- 用useCallback缓存函数
使用react hooks遇见哪些坑?
- useState初始化值,只有第一次有效
- useEffect内部不能修改state
- useEffect可能出现死循环
React hooks闭包陷阱
React Hooks闭包陷阱的根本原因在于函数组件的特性:
- 每次渲染都是独立的闭包:组件函数每次执行都会创建新的作用域
- Hooks捕获的是创建时的值:事件处理函数、定时器回调等捕获的是它们被创建时的状态值
- 依赖数组的不当使用:空依赖数组或依赖项缺失会导致访问旧值
解决方案
- 优先使用函数式更新:在 setState 时使用
prevState => newState - 正确处理依赖数组:确保 useEffect、useCallback、useMemo 的依赖项完整
- 使用 useRef 存储可变值:避免闭包捕获旧值
- 考虑使用 useReducer:对于复杂的状态逻辑
- 使用 eslint-plugin-react-hooks:自动检测依赖项问题
可能被追问的问题
-
useEffect和useLayoutEffect在闭包处理上有区别吗?"
→ 两者在闭包捕获机制上是完全相同的,都是基于当前渲染闭包捕获状态值。主要区别是执行时机:useLayoutEffect 在 DOM 更新后、浏览器绘制前同步执行,useEffect 在绘制后异步执行。这个时机差异会影响用户是否能看到中间状态,但不改变闭包的本质。
-
为什么React要设计成这样?
→ "为了保持函数组件的纯净性和可预测性,每次渲染都是独立的"
-
useMemo和useCallback如何避免闭包问题?"
→ "它们本身是解决方案的一部分,但依赖数组要声明完整"
21.React 18新特性?
React 18的核心是并发渲染(Concurrent Rendering) ,让React可以中断低优先级渲染,优先处理用户交互。
四大核心特性:
- 并发渲染:可中断的渲染机制
- 新Hooks:useTransition、useDeferredValue、useSyncExternalStore
- 流式SSR:边渲染边传输,更快首屏
- 自动批处理增强:Promise、setTimeout等也支持批处理
使用场景:
- useTransition:搜索、过滤等后台任务
- useDeferredValue:大数据计算、复杂渲染
- Suspense:代码分割、数据获取loading状态
react18的并发渲染原理:
第一,可中断的Fiber架构(基础)
React 16引入的Fiber提供了技术可能性——将渲染分解为独立的Fiber节点,支持工作保存与恢复。
第二,优先级驱动的调度器(核心创新)
React 18新增了Lanes优先级模型,将更新分为:
- 紧急更新(用户交互):同步车道,立即执行
- 过渡更新(UI变化):并发车道,可中断
- 空闲更新(预加载):空闲时执行
第三,时间切片与并发协调(执行策略)
React使用5ms为时间单元,主动让出主线程。关键创新是支持并发状态更新——React可以同时准备多个UI版本,选择性地提交和丢弃。
React 18并发渲染是通过基于优先级的可中断渲染和主动时间切片,将同步渲染流水线改造为异步调度系统,确保高优先级更新(用户交互)始终能打断低优先级更新(数据渲染),实现应用级响应性优化。
React 16(fiber)解决了'能不能'中断的问题,React 18解决了'何时该'中断的问题。
22.React性能优化?
React性能优化核心是减少不必要的重新渲染,用React.memo、useMemo、useCallback。然后代码分割懒加载大组件,虚拟列表处理大数据。状态管理要精细化,避免大Context。React 18用useTransition不阻塞用户操作。优化前先用Profiler找到瓶颈
23.react组件通信方式大全?
第一类:父子通信(基础必会)
- Props传递数据:父 → 子
- Callback回调函数:子 → 父
- Ref获取实例:访问子组件方法/数据
第二类:兄弟通信
- 状态提升:将共享状态提到最近的共同父组件
- 通过父组件中转:父组件作为中介传递消息
第三类:跨层级通信(重点)
- Context API:官方跨层级方案,适合主题、用户信息等
- 注意优化:拆分多个Context,useMemo记忆Provider值
第四类:任意组件通信
- 状态管理库:Redux/Zustand用于复杂全局状态
- 事件总线:简单解耦,适合通知型通信
- URL参数:通过路由传递数据
第五类:高级模式
- Render Props:通过prop传递渲染逻辑
- 高阶组件:复用组件逻辑
- 复合组件:组件组合模式
第六类:特殊通信
- Web Workers:主线程与Worker通信
- WebSocket:实时双向通信
总结:React组件通信:父子用Props/Callback,跨层用Context,兄弟用状态提升,全局用Redux,解耦用Event Bus,特殊场景用Ref/URL/WebSocket。根据场景选择最简单合适的方案。
24.react中虚拟DOM和Diff算法原理?
虚拟DOM和Diff算法是React性能的核心机制:
虚拟DOM是真实DOM的JavaScript对象表示,通过内存操作替代昂贵的DOM操作,实现声明式编程和跨平台能力。
Diff算法通过三个优化策略高效比较新旧虚拟DOM:
- 同级比较:不跨层级,复杂度O(n)
- 组件类型判断:不同类型直接替换
- key优化列表:通过key精确复用节点
Fiber架构进一步优化:
- 可中断的Diff过程,不阻塞用户交互
- 双缓存机制避免中间状态
- 并发渲染优先处理高优先级更新
key的重要性:
- key帮助React识别哪些节点可以复用
- 使用唯一稳定标识,避免索引或随机数
- key错误会导致性能问题和bug
实际优化建议:
- 合理使用React.memo、useMemo、useCallback
- 避免在render中创建新引用
- 理解Diff成本,不要过度优化
总结:虚拟DOM是内存中的JS对象,比直接操作DOM快。Diff算法比较新旧虚拟DOM差异,通过同级比较、类型判断、key优化三个策略高效找出最小更新。key帮助识别可复用节点,Fiber架构让Diff可中断不阻塞用户。
25.react中class组件和函数组件的区别?
首先在语法层面,类组件需要继承Component,有render方法;
函数组件就是普通函数,直接返回JSX。
在状态和副作用管理上,类组件用this.state和生命周期方法;
函数组件用useState、useEffect等Hooks,逻辑更集中。
在代码复用方面,类组件常用HOC或Render Props;
函数组件用自定义Hooks,复用性更好。
从发展趋势看,React团队主推函数组件,
新特性都围绕Hooks开发,类组件主要用于维护旧代码。