第 4 卷
使用 Hooks 时的依赖变化:函数组件中, useEffect、 useMemo、 useCallback 等 Hooks 的依赖数组发生变化时,会触发对应的逻辑执行,若逻辑中涉及状态更新,会进一步触发组件渲染。
2. 组件重新渲染时,render 函数内部发生的事情
render 函数(类组件)或函数组件的返回值部分,是组件渲染 UI 的核心逻辑,重新渲染时会执行以下操作:
• 执行 render 函数/函数组件主体:
• 类组件:执行 render 方法,执行过程中会解析组件内部的 JSX 语法,同时计算组件内的变量、
表达式(如 {count + 1})。
• 函数组件:直接执行函数组件的主体代码,从函数开始到 return 语句结束,包括内部变量声明、
Hooks 调用(如 useState 会获取最新状态)、逻辑计算等。
生成虚拟 DOM(VNode)树:render 函数/函数组件的返回值(JSX)会被 Babel 编译为 React.createElement 调用,执行该调用后生成虚拟 DOM 树。虚拟 DOM 是对真实 DOM 的轻
量级描述,包含组件类型、属性(Props)、子节点等信息。
执行 Diff 算法对比新旧虚拟 DOM:React 会将本次生成的新虚拟 DOM 树与上一次渲染保存的旧虚拟 DOM 树进行对比(Diff 算法),找出两者的差异(如节点增删、属性变化、文本内容变化等)。
生成更新补丁并应用到真实 DOM:根据 Diff 算法的对比结果,React 会生成最小化的 DOM 更新补丁(只更新变化的部分),然后通过渲染层(如 react-dom)将这些补丁应用到真实 DOM 中,完成页
面的更新。
注意事项:
• render 函数是「纯函数」(理想情况下),不应包含副作用操作(如修改状态、调用接口、操作
DOM),否则会导致渲染逻辑混乱或性能问题。
• 重新渲染时,若 Diff 算法发现新旧虚拟 DOM 无差异,React 会跳过真实 DOM 的更新操作,这是
React 的核心性能优化点之一。
八、避免 React 中不必要渲染的性能优化方案
不必要的渲染(如父组件渲染导致子组件无意义跟随渲染、状态未变却触发渲染)会浪费性能,尤其是复杂应用中,需通过以下方案优化:
1. 类组件优化方案
• 使用 React.PureComponent:纯组件自动实现了 shouldComponentUpdate 方法,对
• 手动重写 shouldComponentUpdate:对于复杂场景(如嵌套对象的 props),可手动实现
• 避免在 render 中创建新对象/新函数:render 中创建的新对象(如 { name: 'xxx' })、新
函数(如 onClick={() => this.handleClick()})会导致每次渲染时 props 引用变化,即使内容相同,浅比较也会认为 props 变化,触发子组件渲染。解决方案:将对象/函数提升
到组件实例属性(如 this.obj = { name: 'xxx' })或使用箭头函数绑定在构造函数中。
2. 函数组件优化方案
• 使用 React.memo 包装组件: React.memo 是高阶组件,用于函数组件的性能优化,类似类组
件的 PureComponent,会对组件的 props 进行浅比较,只有 props 变化时才触发渲染。用法: const MemoComponent = React.memo(FunctionComponent);若需自定义对比
逻辑,可传入第二个参数(对比函数)。
• 使用 useMemo 缓存计算结果:对于组件内的复杂计算逻辑(如大数据过滤、排序),每次渲染都
会重新执行,浪费性能。 useMemo 可缓存计算结果,仅当依赖数组中的值变化时才重新计算。用法: const result = useMemo(() => computeFn(data), [data])。
• 使用 useCallback 缓存函数引用:函数组件中,每次渲染都会重新创建函数,导致传递给子组件的
函数 props 引用变化,触发子组件不必要渲染。 useCallback 可缓存函数引用,仅当依赖数组变化时才创建新函数。用法: const handleClick = useCallback(() => { ... },
[deps])。
3. 通用优化方案
• 拆分组件为更小的粒度:将复杂组件拆分为多个独立的子组件,使得状态变化时,只有受影响的子
组件重新渲染,而非整个复杂组件。例如:将列表项拆分为独立的 ListItem 组件,单个列表项状态变化时,仅该组件渲染。
• 合理使用 Context 或状态管理库:避免将过多无关状态放入 Context 中,否则 Context 状态变化
时,所有订阅该 Context 的组件都会渲染。可将 Context 拆分为多个细分 Context,或使用 Redux、Zustand 等状态管理库,实现状态的精准订阅(仅依赖该状态的组件才渲染)。
• 避免不必要的状态更新:调用 setState 或 useState 更新函数时,确保传入的新状态与旧
状态确实不同。例如:类组件中可先判断再更新( if (newState !== this.state.count) this.setState({ count: newState }));函数组件中,
useState 会自动忽略与旧状态相同的新状态(基本类型)。
• 使用懒加载和代码分割:通过 React.lazy 和 Suspense 实现组件的懒加载,避免初始渲染
时加载过多无关组件,减少初始渲染时间。用法: const LazyComponent = React.lazy(() => import('./LazyComponent')),配合 Suspense 展示加载占位
九、setState 的调用原理及 React 内部执行流程
1. setState 的调用原理
setState 是 React 类组件中更新状态的核心方法,其核心原理是「触发组件的重新渲染」,但并非直接修改 this.state(直接修改不会触发渲染),而是通过 React 内部的状态更新机制,异步且批
量地处理状态更新,最终完成组件渲染。
关键特性:setState 是「异步更新」(多数场景下)、「批量更新」、「可能被合并」,其原理依赖于 React 的调度系统和协调系统。
2. 调用 setState 后,React 内部发生的事情(详细流程)
- 接收状态更新请求,加入更新队列:
- 调用 setState(partialState, callback) 时,React 首先会接收传入的
partialState(可以是对象或函数),并将其加入到当前组件的「更新队列」中(更新队列是存储组件所有待处理状态更新的队列)。
- 若 partialState 是函数,React 会在后续处理时调用该函数,传入当前的 state 和
props,获取最终的新状态(便于依赖前一个状态计算新状态,如 this.setState(prevState => ({ count: prevState.count + 1 })))。
- 判断是否需要批量更新:
- React 会检查当前执行环境是否处于「批量更新上下文」中(如合成事件回调、生命周期方法、
React 钩子函数内部)。若是,则将当前更新加入批量更新队列,等待所有批量更新收集完成后统一处理;若否(如原生事件、setTimeout、Promise 回调中),则直接触发后续的更新流程
(React18 后通过自动批处理,大部分场景都支持批量更新)。
- 触发调度器(Scheduler)调度更新:
- 批量更新收集完成后,React 会调用调度器(Scheduler)的 scheduleCallback 方法,将状
态更新任务加入调度队列,并根据任务优先级排序(如用户输入是高优先级,普通状态更新是低优先级)。
- 调度器负责时间切片(Time Slicing),确保高优先级任务先执行,避免长时间占用主线程导致页
面卡顿。
- 协调器(Reconciler)执行 Diff 算法,生成 Fiber 树:
- 调度器触发更新任务后,协调器(Reconciler)会启动工作,从组件的根节点开始,基于 Fiber 架
构遍历组件树(可中断遍历)。
- 在遍历过程中,协调器会合并更新队列中的所有状态更新,计算出组件的最终新状态,并对比新
旧虚拟 DOM(Diff 算法),找出组件树中需要更新的部分,生成新的 Fiber 树(Fiber 树是
全站分卷索引 · 28 卷
- 1第 1 卷
- React18 核心面试题详细解答
- 一、React18 核心源码精读
- 1. 核心架构分层(源码核心目录)
- • 调度层(Scheduler):核心包 scheduler,负责任务优先级排序、时间切片(Time
- • 协调层(Reconciler):核心包 react-reconciler,负责虚拟 DOM 对比(Diff 算法)、
- • 渲染层(Renderer):如 react-dom(浏览器端)、 react-native(原生端),负责将协
- 2. 核心新特性源码关键点
- • 核心概念:不再是同步阻塞式渲染,而是可以中断、暂停、恢复或放弃的渲染流程,源码中通过
- • 关键实现:基于 Fiber 架构的「可中断遍历」,Fiber 节点作为最小调度单元,每个 Fiber 节点遍历
- • 核心逻辑:源码中通过 unstable_batchedUpdates 方法封装批处理逻辑,React18 中取消
- • 关键细节:批处理的本质是延迟执行 flushSync(刷新状态更新),当所有批量更新收集完成
- • 核心 API: useTransition、 startTransition,源码中通过标记更新的优先级
- 2第 2 卷
- • 声明式编程(Declarative):开发者只需描述「UI 应该是什么样子」(基于状态),无需关心
- • 组件化思想(Componentization):将复杂 UI 拆分为独立、可复用的组件,组件拥有自己的状
- • 单向数据流(Unidirectional Data Flow):状态(State)只能从父组件传递给子组件(通过
- • 虚拟 DOM(Virtual DOM)与不可变状态(Immutability):
- 2. React 与 Vue 的异同
- 四、React 事件机制(与普通 DOM 事件的区别及工作原理)
- 1. 什么是 React 事件机制
- 2. React 合成事件与普通 DOM 事件的区别
- 3. React 事件机制的工作原理(三步流程)
- • React 在组件挂载时,会遍历组件的事件属性(如 onClick),将其注册为事件委托(React16
- • 注册时,React 会为每个合成事件分配一个唯一的标识,并将事件回调函数与标识关联,存储在内
- 3第 3 卷
- 五、React.Component 与 React.PureComponent 的区别
- 1. 核心区别:shouldComponentUpdate 方法
- • React.Component:默认没有实现 shouldComponentUpdate 方法,该方法默认返回
- ◦ 若需优化性能,开发者需手动实现 shouldComponentUpdate 方法,手动对比
- • React.PureComponent:自动实现了 shouldComponentUpdate 方法,该方法内部会对
- 2. 其他补充差异
- ◦ React.Component:适用于所有类组件场景,尤其是组件的 props 或 state 为复杂嵌套
- ◦ React.PureComponent:适用于组件的 props 和 state 均为基本类型,或为简单对象/数
- ◦ PureComponent 的浅比较存在「局限性」:若 props 或 state 是复杂嵌套对象/数组,
- ◦ React.PureComponent 对应函数组件中的 React.memo( React.memo 是高阶组件,对
- 六、React 的高阶组件(HOC):定义、与普通组件的区别及使用场景
- 1. 什么是高阶组件(HOC)
- 4第 4 卷
- 2. 组件重新渲染时,render 函数内部发生的事情
- • 执行 render 函数/函数组件主体:
- • 类组件:执行 render 方法,执行过程中会解析组件内部的 JSX 语法,同时计算组件内的变量、
- • 函数组件:直接执行函数组件的主体代码,从函数开始到 return 语句结束,包括内部变量声明、
- • render 函数是「纯函数」(理想情况下),不应包含副作用操作(如修改状态、调用接口、操作
- • 重新渲染时,若 Diff 算法发现新旧虚拟 DOM 无差异,React 会跳过真实 DOM 的更新操作,这是
- 八、避免 React 中不必要渲染的性能优化方案
- 1. 类组件优化方案
- • 使用 React.PureComponent:纯组件自动实现了 shouldComponentUpdate 方法,对
- • 手动重写 shouldComponentUpdate:对于复杂场景(如嵌套对象的 props),可手动实现
- • 避免在 render 中创建新对象/新函数:render 中创建的新对象(如 { name: 'xxx' })、新
- 2. 函数组件优化方案
- 5第 5 卷
- 1. 默认异步场景(大部分日常开发场景)
- • 常见场景:
- • React 生命周期方法(如 componentDidMount、 componentDidUpdate、
- • 常见场景:
- 3. React18 中的变化:自动批处理(Automatic Batching)
- 总结
- • 非 React 控制的环境(原生事件、定时器、Promise):React18 前同步,React18 后默认异步
- • 无论同步还是异步,都不建议依赖 setState 后的 this.state 做后续逻辑,如需使用更新
- 十一、React 中 setState 的批量更新过程具体实现
- 1. 核心前提:批量更新上下文标识
- • 进入批量更新上下文时(如合成事件回调、生命周期方法执行前),将 isBatchingUpdates
- • 退出批量更新上下文时(如合成事件回调、生命周期方法执行完成后),将
- 6第 6 卷
- 3. React18 自动批处理(Automatic Batching)的扩展实现
- • 将「批量更新上下文」从「全局标识」改为「基于根节点的上下文管理」(通过 createRoot
- • 在所有异步场景(原生事件、 setTimeout、 Promise 回调等)中,自动包裹批量更新逻辑,
- • 若需强制取消批量更新(立即执行更新),可使用 ReactDOM.flushSync 方法,该方法会强制
- • 核心价值:减少 DOM 操作次数。若没有批量更新,多次 setState 会触发多次渲染,而批量更
- • 注意事项:在批量更新上下文中,无法立即获取 this.state 的最新值(因为状态尚未合并更
- 十二、React 的生命周期及各阶段方法
- 1. 挂载阶段(组件从无到有,首次渲染到 DOM)
- 2. 更新阶段(组件状态/Props 变化,触发重新渲染)
- 3. 卸载阶段(组件从 DOM 中移除,销毁)
- • componentWillUnmount():组件卸载前执行,仅执行一次。常用于:解绑原生 DOM 事件
- 4. 特殊生命周期方法(错误处理)
- 7第 7 卷
- • 优化原理: PureComponent 自动实现了 shouldComponentUpdate 方法,内部对
- • 若浅比较无变化, shouldComponentUpdate 返回 false,阻止组件重新渲染;若有变化则
- • 适用场景:组件的 Props/State 为基本类型或简单引用类型(无嵌套结构),且渲染仅依赖
- • 优化方法 2:手动重写 shouldComponentUpdate:
- • 优化原理:开发者根据组件的实际渲染依赖,自定义对比逻辑,仅在关键属性变化时返回
- • 优势:适用于复杂场景(如嵌套对象的 Props),可通过「深比较关键属性」实现精准优化(注
- 2. 辅助优化阶段:挂载阶段与卸载阶段
- • 挂载阶段优化:componentDidMount 中请求数据:
- • 优化原理:避免在 render 或 constructor 中请求数据(会导致多次请求或阻塞渲染),
- • 卸载阶段优化:componentWillUnmount 中清理资源:
- • 优化原理:若组件卸载前未清理资源(如定时器、原生事件监听、未完成的网络请求),这些资源
- 3. 其他生命周期相关优化:避免在 render 中创建新对象
- 8第 8 卷
- • 实现逻辑:父组件定义回调函数,通过 Props 传递给子组件;子组件触发事件时,调用该回调函
- • 方式 3:父组件获取子组件实例/DOM:Refs 传递:
- • 实现逻辑:父组件通过 useRef(函数组件)或 createRef(类组件)创建 Ref 对象,传递给
- • 适用场景:父组件需要直接操作子组件的 DOM(如获取输入框焦点)或调用子组件的方法(如类
- • 示例(函数组件转发 Ref):
- 2. 兄弟组件通信
- • 方式:通过共同父组件中转:
- • 实现逻辑:兄弟组件的通信需借助它们的共同父组件:兄组件通过回调函数将数据传递给父组件,
- • 适用场景:兄弟组件之间需要共享数据或交互(如兄组件点击按钮,弟组件展示对应数据),且组
- • 示例:
- 3. 跨层级组件通信(祖孙、远亲组件)
- • 方式 1:Context API(React 内置):
- 9第 9 卷
- 1. 对 Redux 的核心理解
- • 单一数据源:整个应用的状态集中存储在一个“状态树”(store)中,避免多个组件分散存储状态
- • 状态不可变:状态(state)是只读的,不能直接修改,只能通过触发“动作”(action)生成新的
- • 使用纯函数修改状态:通过“减速器”(reducer)函数处理状态更新,reducer 是纯函数(无副
- 2. Redux 主要解决的问题
- • 解决跨组件状态共享问题:在 React 中,父子组件可通过 Props 传递状态,但跨层级组件(如祖孙
- • 解决状态变化不可预测问题:组件内部通过 setState 直接修改状态时,状态变化逻辑分散在各
- • 解决异步状态管理复杂问题:React 组件中处理异步逻辑(如网络请求)时,异步结果修改状态的
- • 解决组件复用状态逻辑困难问题:多个组件需要复用相同的状态管理逻辑(如用户登录状态、表单
- 1. Redux 核心工作原理
- • store:全局唯一的状态容器,存储整个应用的状态树(state),提供 getState()(获取状
- • action:状态变化的“动作描述”,是修改状态的唯一触发源,必须是纯对象,且必须包含
- 10第 10 卷
- • 示例(登录异步请求):
- • 核心原理:通过“生成器函数(generator)”编写异步逻辑,将异步流程与组件解耦,支持更复
- • 核心思路:监听特定的 action,当 action 被 dispatch 时,执行对应的生成器函数处理异步逻辑,
- • 核心原理:基于 RxJS 库,将 action 视为“数据流”,通过响应式操作符(如 switchMap、
- 2. Redux 中间件的定义与作用
- 3. 编写 Redux 中间件的步骤(自定义中间件)
- • 第一层函数(store):接收 Redux 的 store 对象,可通过 store.getState() 获取当前状
- • 第二层函数(next):接收“下一个中间件的 dispatch 方法”(若没有下一个中间件,则是原始
- • 第三层函数(action):接收组件 dispatch 的 action,在此层编写核心处理逻辑(如异步、日
- 11第 11 卷
- 12第 12 卷
- 2. Redux 与 Vuex 共有的设计思想
- • 集中式状态管理:将应用中所有组件共享的状态集中存储在一个全局容器(store)中,避免状态分
- • 单向数据流:状态流转遵循“单向”规则,确保状态变化可预测。核心流程均为“组件触发操作
- • 分离状态修改逻辑:将状态修改逻辑与组件 UI 逻辑分离,组件仅负责触发操作(如点击按钮触发
- • 状态可追踪:通过规范的状态修改流程,使得每一次状态变化都有明确的触发源头(如 action 描
- • 组件解耦:通过全局 store 实现组件间的间接通信,无需依赖组件层级关系(如父子、跨层级),
- • 只能在函数组件的顶层调用(不可在条件判断、循环、嵌套函数中调用),确保 Hooks 执行顺序
- • 通过“钩子”的方式将 React 内部能力“注入”到函数组件中,无需编写类组件即可实现复杂功
- • useEffect:用于处理函数组件的副作用(如网络请求、DOM 操作、定时器),替代类组件的生命
- 2. React 团队引入 Hooks 的原因
- • 解决类组件 this 指向混乱问题:类组件中 this 指向动态变化(如事件回调中 this 可能为
- • 解决生命周期逻辑分散与冗余问题:类组件的生命周期方法(如 componentDidMount、
- 13第 13 卷
- 14第 14 卷
- 15第 15 卷
- • 严格遵循 Hook 使用规则:只能在函数组件顶层或其他自定义 Hook 中调用,不可在条件判断、循
- • 自定义 Hook 是逻辑复用,不是组件复用:每个组件调用自定义 Hook 时,内部的内置 Hooks 都
- • 避免过度封装:仅抽取真正复用的逻辑,简单逻辑无需封装为自定义 Hook,否则会增加代码复杂
- • 可组合多个自定义 Hook:一个自定义 Hook 内部可调用其他自定义 Hook,实现更复杂的逻辑
- 二十六、useCallback 和 useMemo 的区别及其使用场景
- 1. 核心定义与本质区别
- • useCallback:缓存的是「函数引用」,返回一个记忆化的函数。当组件重新渲染时,若依赖项未
- • useMemo:缓存的是「函数执行的结果」,返回一个记忆化的值。当组件重新渲染时,若依赖项
- 2. 语法对比
- 3. 关键区别总结
- 4. 使用场景详解
- 16第 16 卷
- 5. 注意事项
- • 不要过度使用:useCallback 和 useMemo 本身也有性能开销(需要存储缓存、对比依赖项),对
- • 依赖数组必须完整:若缓存的函数/计算依赖组件内的变量(如 state、props),必须将这些变量
- • useMemo 不可用于副作用:useMemo 的回调函数必须是纯函数(无 DOM 操作、网络请求等副作
- 二十七、useEffect 与 useLayoutEffect 在 React 中的不同及其使用场景
- 1. 核心区别:执行时机
- • useEffect:「异步执行」,在浏览器完成页面渲染(绘制 DOM)之后执行。React 会先更新
- • useLayoutEffect:「同步执行」,在 React 完成 DOM 更新但尚未将 DOM 绘制到页面之前执
- 2. 执行流程对比
- 3. 关键区别总结
- 4. 使用场景详解
- 17第 17 卷
- 18第 18 卷
- 19第 19 卷
- 20第 20 卷
- 21第 21 卷
- • 优先级调度,保障关键任务优先执行:
- • 为不同更新任务分配优先级,高优先级任务(如用户输入、动画)可打断低优先级任务,优先完成
- • 例如:用户在输入框打字时,输入事件的优先级高于列表数据的普通更新,Fiber 会优先处理输入
- • 减少不必要的计算,提升渲染效率:
- • 可中断的协调过程避免了因任务阻塞导致的重复计算(如 React 15 中若同步协调被打断,重新触
- • Fiber 节点的链表结构便于精准定位中断位置,恢复后继续处理,减少无效计算;同时,Fiber 对
- 三十一、在 React Fiber 中,什么是 Fiber 节点?它如何与传统的 React
- • 组件相关信息: type(组件类型,如函数组件、类组件、div 等原生 DOM 类型)、
- • 状态相关信息: memoizedState(组件的状态数据,如类组件的 state、函数组件的 Hooks 链
- • 链表结构信息: child(指向第一个子 Fiber 节点)、 sibling(指向兄弟 Fiber 节点)、
- • 任务调度信息: priority(当前 Fiber 节点对应的任务优先级)、 effectTag(标记需要执
- • 其他辅助信息: alternate(指向当前 Fiber 节点的备用节点,用于存储更新过程中的临时状
- 22第 22 卷
- • 提交阶段(Commit):同步、不可中断,负责根据协调阶段标记的 effectTag,批量执行真实
- • 优势:协调阶段的异步可中断特性不会影响真实 DOM 状态,提交阶段的同步执行确保 DOM 操作
- 4. 性能优化:无缓存 vs 双缓存机制
- • React 15 协调算法:
- • React Fiber 协调算法:
- • 引入「双缓存机制」,通过 Fiber 节点的 alternate 属性维护两棵 Fiber 树:
- • workInProgress 树:正在进行更新的 Fiber 树,用于执行 Diff 对比、标记 effectTag,更新过程中
- • 更新完成后,通过切换 current 树与 workInProgress 树的引用,完成 DOM 更新,旧的 current
- • 优势:避免重复创建 Fiber 节点,减少内存开销;同时,workInProgress 树的异步构建不会影响
- 5. 关键总结
- • 轻量级:仅包含真实 DOM 的必要信息,不包含浏览器 DOM 节点的复杂属性与方法,操作成本远
- • 内存中存在:虚拟 DOM 是 JavaScript 对象,存储在内存中,操作时不会触发浏览器的重排
- 23第 23 卷
- • 假设 1:同类型的组件产生相似的 DOM 结构,不同类型的组件产生不同的 DOM 结构:
- • 若两个组件类型相同(如都是 <div> 或都是自定义组件 <UserList>),则认为它们的 DOM 结构相
- • 若两个组件类型不同,则直接销毁旧组件对应的 DOM 结构,创建新组件对应的 DOM 结构,无需
- • 假设 2:列表节点通过唯一的 key 属性,可以确定节点在列表中的位置是否变化:
- • 对于列表类子节点(如 <ul> 下的多个 <li>),若未设置 key,React 会通过“索引”对比,可能导
- • 若设置了唯一的 key,React 可通过 key 快速定位节点的新增、删除、移动操作,提升列表对比效
- 2. Diff 算法的核心工作流程:分层对比(深度优先)
- • 节点类型不同(如旧节点是 <div>,新节点是 <p>):
- • 节点类型相同(如新旧节点都是 <div>):
- • 对比节点的属性(props):若属性有变化(如 className、id、事件绑定等),则更新真实
- • 非列表子节点(如 <div> 下的 <span>、<img> 等无序子节点):
- • 列表子节点(如 <ul> 下的多个 <li>,或通过 map 生成的子节点):
- 24第 24 卷
- • 索引 0:旧节点是“列表项 1”,新节点是“新列表项”,类型相同,React 会复用旧节点的 DOM
- • 结果:若原列表项 1 的输入框有内容,插入新节点后,新列表项的输入框会保留原列表项 1 的内
- • Diff 算法认为索引 0 的节点从 A 变为 B(销毁 A,创建 B),索引 1 的节点从 B 变为 A(销毁 B,
- • key 必须唯一:同一列表中不能有重复的 key,否则 React 会报警告,且 Diff 算法无法正确匹配节
- • key 必须稳定:key 不应随渲染次数变化(如不推荐用 Math.random() 生成 key),否则每次渲染
- • 避免用索引作为 key:仅当列表是“静态列表”(无插入、删除、排序操作)且无状态时,可临时
- 5. 与 Diff 算法的关系总结
- 1. 核心设计思路差异:“整体协调” vs “精准靶向”
- • React Diff 算法:
- • 核心思路是「整体协调(Reconciliation)」,更侧重“任务调度的兼容性”。由于 React 引入了
- • 设计前提是「组件类型决定 DOM 结构相似性」:若组件类型不同,直接销毁旧组件 DOM 树并重
- • Vue Diff 算法:
- 25第 25 卷
- • 设计前提是「模板编译优化」:Vue 基于模板语法开发,编译阶段会对模板进行静态分析(如标记
- 2. 子节点对比策略差异(核心差异点)
- • React:
- • key 的核心作用是「建立新旧节点的映射关系」,用于判断节点的“新增/删除/移动”。但 React
- • 4. 对于需要移动的节点,通过“最长递增子序列”计算最小移动步数(React16+ 引入,优化移动
- • Vue:
- • Vue2 和 Vue3 的列表 diff 均对“移动优化”做了深度适配,尤其是 Vue3 的“最长递增子序列”优
- • 1. Vue2:采用“双指针法”遍历新旧列表,通过 key 对比节点,同时记录节点移动、新增、删除
- • 2. Vue3:在 Vue2 基础上优化,通过“最长递增子序列”确定无需移动的节点,仅对其他节点进行
- • React:无编译阶段的静态节点标记,即使节点属性和内容完全不变(如静态文本、固定样式节
- • Vue:编译阶段会标记“静态节点”(如 <span>静态文本</span>)和“静态根节点”(包含多个
- • React:
- 26第 26 卷
- • 流式 SSR:服务端可分块发送 HTML 内容,先发送静态部分,再发送动态数据加载完成的部分,减
- • suspense fallback:在服务端渲染时,若组件数据未加载完成,可先渲染 fallback 占位符(如加
- 3. 开发体验与稳定性优化
- 4. 其他重要特性
- • useId:生成跨服务端和客户端的唯一 ID,解决服务端渲染时 ID 不匹配的问题(如表单标签关联、
- • useDeferredValue:延迟更新非紧急状态,类似 useTransition,但更侧重“值的延迟传递”(如
- 三十八、在 React 17 中,事件处理机制经历了哪些变化?特别是与之前版
- 1. 核心变化一:事件委托目标从 document 改为根节点(root)
- • 当用户触发真实 DOM 事件时,事件冒泡到 document 节点,被 React 统一的事件监听器捕获,再
- • 1. 若第三方库或原生代码修改了 document 的事件监听器(如阻止冒泡),会干扰 React 事件的
- • 3. 多个 React 应用共存时(如同一页面嵌入多个独立 React 应用),所有应用的事件都会委托到
- • 将事件委托的目标从 document 改为「应用的根节点」(即 ReactDOM.render 挂载的节点,如
- 27第 27 卷
- 28第 28 卷
- 四十、React 18 引入了新的协调器(Concurrent Mode)。请从源码角度
- 1. 源码核心依赖模块
- •(1)Scheduler(调度器):源码位置 react-scheduler,负责任务的优先级排序、时间切
- •(2)Reconciler(协调器):源码位置 react-reconciler,基于 Fiber 架构,负责组件树的
- •(3)Lane 模型(优先级模型):源码中通过二进制位表示任务优先级(如 ImmediateLane 最
- 2. 工作原理核心流程(源码视角)
- • 可中断判断: shouldYield() 函数由 Scheduler 提供,判断当前时间切片是否用尽(默认
- • 任务恢复:当高优先级任务执行完成或时间切片重新分配后,调度器会再次调用 workLoop,从
- • 任务放弃:若在中断期间,有更高优先级的更新任务覆盖了当前任务(通过 Lane 模型判断),则
- •(1)时间切片(Time Slicing):Scheduler 基于 requestIdleCallback 模拟实现时间切
- •(2)优先级抢占:通过 Lane 模型标记任务优先级,高优先级任务可在低优先级任务执行过程
- 4. 源码层面的关键设计:双缓存 Fiber 树