React面试题2

张开发
2026/5/5 10:15:01 15 分钟阅读
React面试题2
文章目录1. React生命周期2. React Fiber核心思想可中断的异步渲染双缓存技术时间分片为什么需要Fiber为什么可以中断3. React 高阶组件HOC为什么要用 HOC4. 哪些变化会触发函数的重新渲染如何避免5. 常用hooks6. useEffect6.1. useEffect使用6.2. useEffect闭包陷阱解决方案7. useEffect 与 useLayoutEffect什么时候必须用 useLayoutEffect8. useState与useRef区别何时使用useRef9. useCallback和useMemo区别10.React.memo与useMemo的区别11. React的hooks原理为什么 Hook 顺序不能变12. Reconciler协调器Reconciler 的演进从 Stack 到 Fiber早期Stack Reconciler (React 15 及以前)现状Fiber Reconciler (React 16)如何遍历fiber树13. React 调度14. 虚拟 DOM (VDOM)本质是什么虚拟 DOM 的工作原理VDOM 一定比原生 DOM 快吗15. React中的Diff算法16. React响应式原理17. React SSR为什么需要 SSRSSR 的工作流程SSR 对比 CSR18. 什么是水合Hydration什么是水合错误常见原因19. React通信方式19.1. 父子组件通信19.2. 跨级组件通信 (Context API)19.3. 兄弟组件通信20. 类组件与函数组件的区别21. React各个版本新特性21.1. React 1621.2. React 1721.3. React 1821.4. React 191. React生命周期生命周期的三个阶段挂载阶段 (Mounting)组件实例被创建并插入 DOM 的阶段类组件constructor-getDerivedStateFromProps-render-componentDidMount(适合发请求、开启定时器)。函数组件函数体执行 - 渲染 -useEffect(() { ... }, [])更新阶段 (Updating)当 Props 或 State 发生变化时组件重新渲染的阶段类组件shouldComponentUpdate-render-getSnapshotBeforeUpdate-componentDidUpdate。函数组件函数体重新执行 -useEffect(() { ... }, [deps])。卸载阶段 (Unmounting)组件从 DOM 中移除的阶段类组件componentWillUnmount(清除定时器、解绑事件)函数组件useEffect的返回函数 (Cleanup)。2. React FiberFiber就是将React 渲染变成“可中断恢复、可插队”的异步任务解决老版 React 渲染阻塞同步且不可中断的问题Fiber是 React 16 引入的全新架构旨在解决 V15 在处理大型组件树时产生的卡顿问题将 React 的核心算法从同步递归演变为异步可中断的并发调度Fiber 的设计目标是提高 React 的并发性和响应性通过时间分片优化复杂组件树的渲染性能核心思想可中断的异步渲染Fiber 引入了“时间分片Time Slicing”的概念核心目标是将耗时较长的JS计算任务拆分成多个微小的片段分布在浏览器每一帧的空闲时间内执行任务拆分将大的更新任务拆解为许多微小的“工作单元”优先级调度浏览器在每一帧的空闲时间requestIdleCallback执行这些单元可中断与恢复如果此时有高优先级的任务如用户点击、输入React 会暂停渲染优先响应用户等浏览器空闲后再恢复执行双缓存技术current 树当前屏幕上显示的树workInProgress 树正在内存中构建的树当workInProgress树在后台构建并比对完成后React 只需简单地交换一下指针新的树就变成了current树瞬间完成替换时间分片时间分片 (Time Slicing)是 React Fiber 架构中最核心的“调度策略”。它的本质是将一个耗时极长的同步任务拆分为多个耗时极短的小任务并在每一个小任务执行完后把主线程的控制权交还给浏览器核心原理React 的Scheduler调度器默认将一个时间片Time Slice定义为5ms开始任务React 开始深度优先遍历 Fiber 树。检查时间每处理完一个 Fiber 节点React 都会通过shouldYield()函数检查“从开始到现在是否已经过去了 5ms”中断与让出 (Yield)如果时间没到继续处理下一个 Fiber 节点如果时间到了React 立即停止遍历保存当前进度的指针并调用MessageChannel发送一个宏任务浏览器执行此时当前 JavaScript 执行栈清空浏览器可以利用这个空档进行Layout布局、Paint绘制或响应用户输入恢复执行由于MessageChannel产生的宏任务进入了队列浏览器忙完后会执行它React 重新回到刚才保存的指针处继续下一个 5ms 的工为什么需要Fiber在React 16之前的版本中是使用递归的方式处理组件树更新称为堆栈调和Stack Reconciliation这种方法一旦开始就不能中断直到整个组件树都被遍历完。这种机制在处理大量数据或复杂视图时可能导致主线程被阻塞从而使应用无法及时响应用户的输入或其他高优先级任务为什么可以中断结构基础从“树”到“链表”在旧版 React 中协调Reconciliation是基于递归的Fiber把组件树转换成了一个**虚拟的单链表结构**React 只需要用一个全局变量如nextUnitOfWork记录下当前处理到了哪个节点执行机制从“死循环”到“可循环”Fiber 的执行核心是一个while循环而不是递归函数协作式调度3. React 高阶组件HOCHOC是一个函数它接收一个组件并返回一个新组件为什么要用 HOC逻辑复用比如多个页面都需要判断用户是否登录、是否拥有权限属性代理Props Proxy向被包裹的组件注入额外的 props如主题颜色、用户信息、多语言配置渲染劫持根据条件决定是否渲染组件或者在渲染前后包裹一些额外的 UI如加载动画4. 哪些变化会触发函数的重新渲染如何避免触发因素描述优化/阻断方式State组件内部状态改变Object.is 相同值跳过Props父组件传递的属性改变React.memo (浅比较)Parent父组件自身发生重渲染React.memoContext订阅的上下文值改变拆分 Context / useMemo5. 常用hooks基础逻辑 HooksuseState状态管理useEffect处理副作用useRef访问 DOM 与持久化数据性能优化 HooksuseMemo缓存计算结果useCallback缓存函数引用useContext跨组件传递数据6. useEffect6.1. useEffect使用useEffect主要用于处理副作用Side Effects通俗点说只要是不直接参与UI渲染的任务如请求数据、操作 DOM、定时器、监听事件都应该放在useEffect里useEffect的核心是同步外部系统。它通过依赖数组来模拟生命周期空数组对应挂载有变量对应更新。最需要注意的是清理函数是防止内存泄漏的关键基本语法结构useEffect接收两个参数回调函数包含你要执行的副作用逻辑依赖数组可选决定什么时候重新执行这个回调useEffect(() { // 1. 这里执行副作用逻辑 (如: console.log, API 请求) return () { // 2. [可选] 清理函数 (如: 清除定时器、取消订阅) }; }, [/* 3. 依赖项 */]);6.2. useEffect闭包陷阱由于 Hook 的闭包特性useEffect内部的函数“捕捉”到了某一时刻的变量快照而没有获取到最新的值假设我们要实现一个每秒自动加 1 的计数器function Counter() { const [count, setCount] useState(0); useEffect(() { const timer setInterval(() { // 这里的 count 永远是 0 console.log(Current count:, count); setCount(count 1); }, 1000); return () clearInterval(timer); }, []); // 依赖项为空数组 return h1{count}/h1; }结果屏幕上的数字从 0 变成 1 后就再也不动了。控制台会一直打印Current count: 0原因分析useEffect在组件首次挂载时执行此时内部的匿名函数闭包捕获了当时的count变量其值为0虽然setCount触发了重新渲染但由于依赖项是[]useEffect不会重新执行setInterval里的回调函数依然指向第一次渲染时的闭包它看到的count始终是解决方案添加依赖项将count放入依赖数组。每次count变化useEffect都会销毁旧的定时器并重新开启useEffect(() { const timer setInterval(() { setCount(count 1); }, 1000); return () clearInterval(timer); }, [count]); // 依赖 count缺点会导致定时器频繁销毁和重建。如果内部逻辑很重如 WebSocket 连接频繁重建会有性能开销函数式更新 (推荐)setCount接受一个回调函数参数是最新的状态值。这种方式不依赖外部闭包useEffect(() { const timer setInterval(() { // prevCount 永远是实时的不依赖闭包里的 count 快照 setCount(prevCount prevCount 1); }, 1000); return () clearInterval(timer); }, []); // 依赖项依然可以是 []使用useRefuseRef返回的是一个可变的引用对象其.current属性指向同一个内存地址不受组件重绘快照的影响。const countRef useRef(0); countRef.current count; // 每次渲染同步更新 ref useEffect(() { const timer setInterval(() { console.log(countRef.current); // 永远能拿到最新的值 }, 1000); return () clearInterval(timer); }, []);7. useEffect 与 useLayoutEffect特性useEffectuseLayoutEffect执行时机屏幕渲染后屏幕渲染前阻塞渲染不会异步会同步优先场景绝大多数场景API请求、订阅、日志涉及 DOM 测量 或 修改 DOM 避免闪烁性能影响较小不阻塞 UI 响应较大逻辑过重会导致页面卡顿什么时候必须用useLayoutEffect需要根据DOM的物理尺寸宽高、位置来更新状态场景防止视觉闪烁假设你要给一个元素设置随机颜色但必须根据它渲染后的实际宽度来决定。使用useEffect浏览器绘制了初始颜色的 DOM。useEffect异步执行计算宽度修改颜色。浏览器重新绘制新颜色。结果用户会看到颜色跳了一下闪烁。使用useLayoutEffectReact 更新 DOM。useLayoutEffect同步执行计算宽度修改颜色。浏览器接收到最终的颜色指令进行唯一一次绘制。结果用户直接看到最终颜色没有闪烁8. useState与useRef区别特性useStateuseRef触发渲染会触发组件重新渲染不会触发组件重新渲染存储内容组件的状态如输入框内容、列表数据组件的引用/持久化数据如 DOM、定时器 ID访问方式直接读取变量如 count通过 .current 属性读取如 ref.current更新方式异步通过 setCount同步直接修改 ref.current xxx主要用途驱动 UI 变化的响应式数据跨渲染周期保存数据且不影响 UI何时使用useRef访问 DOM 元素让某个输入框自动获得焦点 (focus)、测量 DOM 的尺寸或滚动位置存储“跨渲染周期”的变量不触发重绘定时器 ID、上一次的 Props、防抖变量动画和滚动位置9. useCallback和useMemo区别钩子缓存的对象返回值常见用途useMemo计算结果 (Value)函数执行后的返回值避免复杂的数学计算、过滤大型数组、生成配置对象useCallback函数本身 (Function)函数定义本身保持函数引用一致防止子组件因为 props 中的函数改变而重新渲染10.React.memo与useMemo的区别React.memoReact.memo是一个**高阶组件 (HOC)**包裹一个组件用来防止该组件在 Props 没有变化的情况下进行不必要的重渲染触发时机当父组件重渲染时React 会对比传给子组件的 Props。如果 Props 没变子组件就不会重新执行对比机制默认进行浅比较若传递对象或数组需结合useMemouseMemouseMemo是一个Hook。它在组件内部使用用来缓存一个复杂的计算结果触发时机仅当依赖项数组中的值发生变化时才会重新计算API作用React.memo缓存组件当父组件重新渲染时若子组件的 props 未变化阻止子组件重新渲染useMemo缓存计算结果当依赖项未变化时避免重复执行复杂计算如过滤列表、复杂对象处理useCallback缓存函数引用当依赖项未变化时返回同一个函数实例避免子组件因函数引用变化而重新渲染11. React的hooks原理利用闭包和链表数据结构将状态持久化在组件对应的 Fiber 节点上Hooks 依赖于 React 的 Fiber 架构。每个函数组件对应一个 Fiber 节点Hooks 的状态和副作用通过 Fiber 的memoizedState属性存储和管理。Fiber 节点结构const fiber { tag: FunctionComponent, // 函数组件类型 type: Counter, // 组件函数 memoizedState: null, // Hooks 状态链表 updateQueue: null, // 更新队列 child: null, // 子 Fiber sibling: null, // 兄弟 Fiber return: null // 父 Fiber };memoizedState保存 Hooks 的状态以链表形式存储调用顺序React 通过 Hooks 的调用顺序定位每个 Hook 的状态为什么 Hook 顺序不能变react 并不通过变量名来识别 Hook而是靠执行顺序Hooks的工作流程初次渲染Mount依次创建 Hook 对象并按顺序串成链表并将其绑定到 Fiber更新阶段Update根据调用顺序复用之前的 Hooks 状态处理更新逻辑这就是为什么必须在组件的顶层调用 Hooks并且不能在循环、条件或嵌套函数中调用 Hooks12. Reconciler协调器在 React 中Reconciler**协调器**的核心任务是负责Diff 算法决定在状态更新时哪些 DOM 节点需要被创建、修改或删除简单来说Reconciler 就是在Virtual DOM虚拟 DOM和Renderer渲染器如 ReactDOM之间的桥梁Reconciler 的演进从 Stack 到 Fiber早期Stack Reconciler (React 15 及以前)工作方式递归。一旦开始更新它会同步地遍历整个组件树问题由于 JavaScript 是单线程的如果组件树非常大递归更新会长时间占用主线程。这会导致浏览器无法处理用户输入或动画造成掉帧卡顿现状Fiber Reconciler (React 16)工作方式循环 可中断。它将更新任务拆解成许多微小的“工作单元”即Fiber 节点核心能力可中断如果浏览器有更高优先级的任务如点击事件React 可以暂停当前的计算优先级控制不同的更新如动画 vs 数据落地有不同的优先级如何遍历fiber树为了避免递归带来的调用栈溢出风险React 采用了深度优先搜索DFS的迭代实现通过 Fiber 节点间的三个指针return、child、sibling构建了一个双向链表结构13. React 调度方案结果结论setTimeout(0)强制 4ms 延迟浪费帧时间舍弃Promise.then属于微任务会阻塞渲染直到清空舍弃rAF执行时机在渲染前控制权不在 React 手里不适合调度MessageChannel宏任务、极低延迟、不阻塞渲染最终选择14. 虚拟 DOM (VDOM)本质是什么虚拟 DOM 本质上是一个轻量级的JavaScript对象。它用tag、props和children等属性来描述真实 DOM 的结构用 JavaScript 对象来描述真实 DOM 结构通过事务处理机制将多次DOM修改的结果一次性的更新到页面上从而有效的减少页面渲染的次数减少修改DOM的重绘重排次数提高渲染性能虚拟 DOM 的工作原理第一步创建Render当组件第一次渲染Mount时React 会执行render函数或函数组件体生成一棵虚拟DOM树本质把你的 JSX 代码转换成一个普通的 JS 对象真实DOM 包含成百上千个属性onclick,style,offsetWidth等非常沉重虚拟DOM 只记录必要的属性type,props,children非常轻量第二步比对Diffing当状态State改变时React 会生成一棵新的虚拟DOM树。然后它会将“新树”与“旧树”进行对比找出真正发生变化的地方深度优先遍历React 会从根节点开始采用深度优先遍历的方式比对两棵树生成Patch对象比对过程中如果发现节点属性变了、节点删除了或位置换了就会把这些差异记录在一个Patch补丁对象中Diff 算法能以 O(n) 的复杂度算出差异而不是传统算法的 O(n^3)第三步更新PatchingReact 只会将计算出的差异点Patch应用到真实的 DOM 上例子如果你只是改了列表里的一个文字React 只会更新那一个span的innerText而不会重排整个列表VDOM 一定比原生 DOM 快吗不一定如果你只需要修改一个按钮的颜色直接用document.getElementById(...).style绝对比 React 走一遍 VDOM 流程要快虚拟 DOM 的真正价值在于在处理大规模、复杂的数据更新时它能提供一个“足够好”的性能保底15. React中的Diff算法diff 算法主要基于三大策略分层求异 (Tree Diff)同级节点对比​ React 只会对同级节点进行比对如果一个 DOM 节点在一次更新中跨越了层级比如从div搬到了span里面React 不会尝试移动它而是直接销毁旧的创建新的同类识别 (Component Diff)类型对比如果两个组件的类型相同比如都是UserProfile /则继续递归比对它们的子节点如果类型不同比如从Header /变成了Nav /React 会判定它们是完全不同的子树直接销毁原组件及其所有子节点并挂载新组件唯一标识 (Element Diff Key)Key对比​ 当同级有一堆类似的节点如列表时React 通过key来判断哪些节点是稳定的哪些是新来的16. React响应式原理状态变化触发组件重新执行 → 生成新虚拟 DOM → Diff 算法找出最小差异 → 批量更新真实 DOM → 执行副作用React 的响应式原理可分为触发Trigger - 渲染Render - 协调Reconciliation - 提交Commit四个阶段触发阶段从setState开始当你调用setCount(count 1)时你并不是修改了内存里的一个变量而是触发了一次重新执行组件函数的请求React 会把这些请求收集起来存放在组件对应的Fiber 节点的更新队列中渲染阶段生成虚拟 DOM 快照一旦触发更新React 会重新运行你的函数组件构建虚拟 DOM (VDOM)协调阶段Diff 算法React 拥有两棵树Current Tree当前屏幕上的 和WorkInProgress Tree内存中新生成的。DiffingReact 对比这两棵树找出差异三个假设优化核心TreeDiff对同级节点进行比对Component Diff组件类型变了直接销毁重建Element Diff通过key属性识别列表中的元素避免无谓的删除和新建提交阶段计算出差异后React 进入 Commit 阶段同步更新React 会一次性把所有的差异Patch应用到真实 DOM 上副作用执行在 DOM 更新完成后React 会执行useEffect等钩子函数17. React SSRSSR(Server-Side Rendering)即服务端渲染为什么需要 SSRSEO搜索引擎优化太差搜索引擎的爬虫像百度、旧版 Google来到你的网站只看到一个div idroot/div。它等不及 JS 执行完就走了导致你的网站搜不到首屏加载慢白屏时间长用户必须等 JS 加载完、执行完、发请求拿回数据才能看到内容。在网速慢的手机上用户会盯着白屏好几秒SSR 的工作流程SSR 的本质是“分两步走”服务端生成HTML 用户请求 URL服务器运行 React 代码将组件转换成静态 HTML 字符串使用renderToString。用户瞬间就能看到完整的网页内容虽然此时还不能点客户端激活 (Hydration) 浏览器下载 JS 文件并运行。React 会认领服务器传来的 HTML把事件监听器如onClick重新绑定上去。这个过程叫“水合” (Hydration)SSR 对比 CSR特性客户端渲染 (CSR)服务端渲染 (SSR)首屏可见速度慢需等 JS 下载执行快直接显示 HTMLSEO 友好度差极好服务器压力小服务器只发静态文件大服务器每次都要计算渲染开发复杂度低高需处理 Node.js 环境兼容性18. 什么是水合Hydration水合是指客户端渲染时React 会尝试重用服务端渲染生成的 HTML 结构然后在其上进行事件绑定和状态恢复简单来说服务器先渲染出 HTML 页面SSR客户端浏览器加载 React 脚本React 对已有的 HTML 进行水合Hydration让页面变得可交互什么是水合错误通常这种问题是由于在客户端与服务端之间的组件渲染结果不一致所引起的可能的原因包括在客户端和服务端渲染时组件的状态或属性发生了变化导致生成不同的 HTML 结构在客户端渲染时组件的 DOM 结构被修改或重置与服务器端渲染的 DOM 结构不匹配常见原因依赖于浏览器API的代码在服务器上不可用的 API如window、document依赖于时间的代码如new Date()、Math.random()条件渲染基于客户端特定条件的渲染逻辑19. React通信方式19.1. 父子组件通信**父传子(Props)**父组件通过属性将数据传递给子组件const Child ({ name }) p项目名称{name}/p; const Parent () Child nameReact Pro /;**子传父(回调函数)**父组件通过props传递一个函数给子组件子组件通过调用该函数并传入参数实现数据的“逆流”const Child ({ onShowMsg }) ( button onClick{() onShowMsg(来自子的问候)}点击汇报/button ); const Parent () { const handleMsg (msg) console.log(msg); return Child onShowMsg{handleMsg} /; };19.2. 跨级组件通信 (Context API)Provider发布数据Consumer / useContext订阅数据示例import React, { createContext, useContext } from react; const ConfigContext createContext(); const DeepGrandChild () { // 使用 useContext 直接获取无需 Consumer 包裹 const config useContext(ConfigContext); return div配置项{config.theme}/div; }; const App () ( ConfigContext.Provider value{{ theme: Dark }} IntermediateComponent / /ConfigContext.Provider );19.3. 兄弟组件通信状态提升将共享状态移动到它们共同的父组件中20. 类组件与函数组件的区别特性类组件 (Class)函数组件 (Function)逻辑复用使用 HOC (高阶组件) 或 Render Props使用 Custom Hooks (更简洁)状态管理this.state / this.setStateuseState Hook生命周期具体的生命周期钩子 (如 componentDidMount)useEffect 统一处理副作用性能实例化开销略大轻量闭包机制this绑定问题类组件需要频繁地使用.bind(this)或者箭头函数来确保this指向正确。而函数组件完全没有this的概念数据通过作用域直接获取生命周期方法类组件有明确的生命周期方法函数组件通过useEffect()Hook 来替代生命周期方法21. React各个版本新特性21.1. React 16**Fiber架构**React 16引入了全新的Fiber架构这是其最核心的变化之一。Fiber将渲染过程拆分为多个可中断的小任务避免了长时间占用主线程从而提高了应用的响应性和性能**错误边界Error Boundaries**允许开发者捕获组件树中的JavaScript错误防止整个应用崩溃。**Portals**允许将子节点渲染到存在于父组件之外的DOM节点上Hooks16.8使函数组件能够使用状态和其他React特性包括useState、useEffect、useContext等极大简化了组件逻辑复用和代码组织**Fragments**允许组件返回多个子元素而无需添加额外的DOM节点**React.memo**用于函数组件的性能优化通过浅比较props来避免不必要的重渲染**React.lazy和Suspense16.6**实现了代码分割和组件的动态导入减少了初始bundle大小121.2. React 17自动批处理Automatic Batching这意味着所有的生命周期方法和状态更新都会被批处理这可以减少不必要的渲染次数提高性能**函数组件支持Context**这使得函数组件能够像类组件一样使用Context**增强的事件处理**使得事件处理更加稳定和可靠**新的并发模式**使得React能够更好地处理大量数据和复杂的状态**性能优化**包括减少不必要的渲染和提升组件的初始化速度**事件委托变更**React 17将事件监听从document迁移到React应用的根容器提升了多React版本共存的兼容性**逐步升级支持**React 17支持同一页面中运行多个React版本为渐进式升级提供了可能21.3. React 18并发渲染Concurrent RenderingReact 18最引人注目的特性之一是并发渲染。并发渲染允许React在渲染过程中中断和恢复从而提高应用的响应性和性能自动批处理Automatic Batching在React 18中自动批处理得到了扩展几乎所有状态更新包括useState、useReducer和useContext都会被自动批处理新的根APIcreateRootReact 18引入了一个新的根APIcreateRoot用于替代ReactDOM.render。使用createRoot可以启用并发特性新的钩子useId、useDeferredValue、useTransitionReact 18引入了useId、useDeferredValue和useTransition等新的钩子为开发者提供了更多灵活性**性能优化和底层改进**React 18采用了新的渲染引擎React Reconciler提升了性能和可扩展性**Suspense增强**React 18增强了Suspense的功能支持服务端Suspense和SuspenseList更好地控制多个Suspense组件的加载顺序流式服务端渲染Streaming SSRReact 18引入了renderToPipeableStream支持服务端流式渲染提升首屏加载性能**useSyncExternalStore**供第三方状态管理库在并发渲染下安全地读取外部存储。**useInsertionEffect**专为CSS-in-JS库设计用于在DOM插入前注入样式21.4. React 19React CompilerReact 19引入了React Compiler这是其最核心的变化之一。编译器在构建时自动优化组件重渲染开发者不再需要手动使用useMemo、useCallback和React.memo显著降低了性能优化的心智负担**Actions**React 19引入了Actions这是一套统一的异步操作处理模式。通过useActionState管理异步状态pending、成功、失败useFormStatus在子组件中获取表单提交状态useOptimistic实现乐观更新提升用户体验服务端组件RSC稳定React 19将Server Components提升为一等公民配套完善了数据获取APIcache、cacheSignal增强了流式SSR支持**Activity组件**React 19引入了Activity组件支持隐藏时保留组件状态类似keep-alive功能适用于Tab切换、路由缓存等场景**use API**允许在组件中直接读取Promise或Context无需useEffect和useState简化了数据消费逻辑**文档元数据管理**React 19支持在组件内直接写入title、meta等文档元数据标签React会自动将其提升到head中并完整支持SSR**useEffectEvent**React 19引入了useEffectEvent允许从Effect中提取非响应式逻辑避免Effect因依赖变化而重新执行**支持Web Components**React 19增强了对Web Components的兼容性能够更好地与自定义元素协作**性能工具增强**React 19在React DevTools中新增了性能面板支持追踪组件渲染耗时和并发调度细节同时引入Owner Stack在开发模式下显示组件所有者堆栈

更多文章