提供做网站企业学ui哪家培训机构好

张小明 2025/12/25 5:17:38
提供做网站企业,学ui哪家培训机构好,校园推广方式,阿里云域名注册入口各位专家同仁#xff0c;大家好。今天我们共同探讨一个在现代前端开发中至关重要的议题#xff1a;React 组件的性能优化。尤其是在大型复杂应用中#xff0c;理解组件在不同生命周期阶段的耗时#xff0c;是诊断和解决性能瓶颈的关键。我们将深入研究如何利用浏览器原生的…各位专家同仁大家好。今天我们共同探讨一个在现代前端开发中至关重要的议题React 组件的性能优化。尤其是在大型复杂应用中理解组件在不同生命周期阶段的耗时是诊断和解决性能瓶颈的关键。我们将深入研究如何利用浏览器原生的Performance API精确地记录 React 组件在 Render、Pre-commit 和 Commit 这三个核心阶段的耗时。本次讲座将从Performance API的基础概念讲起逐步深入到 React 的渲染机制最终展示如何通过自定义 Hooks 和全局PerformanceObserver构建一套实用的性能监控方案。1. 性能优化为何重要React 渲染机制概览在深入技术细节之前我们首先要明确为什么关注性能以及 React 是如何工作的。1.1 用户体验与业务价值性能不仅仅是技术指标它直接影响用户体验、转化率和品牌形象。一个响应迅速、流畅的应用能让用户感到愉悦提高留存率反之卡顿、延迟的应用则可能导致用户流失。对于 React 应用而言这通常表现为组件渲染过慢、不必要的渲染或复杂的计算阻塞了主线程。1.2 React 的协调Reconciliation与渲染流程React 采用了一种称为“协调”Reconciliation的机制来高效地更新 UI。当组件的state或props发生变化时React 会进行以下核心步骤触发更新通过setState、useState、forceUpdate或props变化等方式。Render 阶段渲染阶段React 调用组件的render方法对于类组件或执行函数组件体。它根据新的state和props构建一棵新的虚拟 DOM 树Fiber 树。这个阶段是纯计算的不涉及任何实际的 DOM 操作。React 会比较新旧 Fiber 树找出需要更新的部分。此阶段可能会被中断例如更高优先级的更新到来。Pre-commit 阶段预提交阶段在 React 实际修改 DOM 之前它会执行一些操作例如调用getSnapshotBeforeUpdate生命周期方法针对类组件获取 DOM 变更前的快照或准备处理ref。这个阶段非常短暂且发生在 DOM 变更之前因此被称为“预提交”。Commit 阶段提交阶段React 将 Render 阶段计算出的差异应用到实际的浏览器 DOM 上执行 DOM 的添加、更新和删除操作。DOM 更新完成后React 会调用相应的生命周期方法如componentDidMount、componentDidUpdate或执行useLayoutEffect、useEffectHook 的回调函数。useLayoutEffect在浏览器进行绘制之前同步执行而useEffect则在浏览器绘制之后异步执行。理解这三个阶段的边界和职责是精确测量性能的基础。2.Performance API基础浏览器性能测量利器Performance API是现代浏览器提供的一套强大的 API允许开发者以高精度测量网页和应用程序的性能。它比简单的console.time/console.timeEnd更为强大和灵活能够记录时间戳、持续时间并与 DevTools 集成。2.1performance.mark()标记关键时间点performance.mark()方法用于在浏览器的性能时间线上创建一个命名的标记。performance.mark(myComponent:render:start); // ... 执行一些操作 ... performance.mark(myComponent:render:end);name(字符串): 标记的唯一名称。options(对象, 可选): 可以包含detail属性用于存储与标记相关的额外数据。2.2performance.measure()测量时间段performance.measure()方法用于测量两个标记点之间或一个标记点到一个时间点之间的持续时间。performance.mark(myComponent:render:start); // ... performance.mark(myComponent:render:end); // 测量并记录 myComponent:render 的持续时间 performance.measure(myComponent:render:duration, myComponent:render:start, myComponent:render:end);name(字符串): 测量结果的名称。startOrMeasureOptions(字符串 | 对象):如果为字符串表示起始标记的名称。如果为对象可以包含start(起始标记名称或时间戳) 和end(结束标记名称或时间戳) 属性。end(字符串 | 数字, 可选): 结束标记的名称或时间戳。2.3PerformanceObserver异步收集性能数据performance.mark()和performance.measure()会将数据存储在浏览器的性能缓冲区中。为了不阻塞主线程并能高效地收集这些数据我们应该使用PerformanceObserver。它允许我们订阅特定类型的性能事件并在它们发生时异步地接收通知。const observer new PerformanceObserver((list) { list.getEntries().forEach((entry) { console.log(Entry Name: ${entry.name}, Type: ${entry.entryType}, Duration: ${entry.duration.toFixed(2)}ms); // 可以将数据发送到后端分析服务 }); // 清除已处理的标记和测量防止内存泄漏 // performance.clearMarks(); // performance.clearMeasures(); }); // 监听 mark 和 measure 类型的性能事件 observer.observe({ entryTypes: [mark, measure] }); // 在不再需要时断开观察者 // observer.disconnect();PerformanceObserver构造函数接收一个回调函数当有新的性能条目可用时该函数会被调用。list.getEntries()返回一个PerformanceEntry对象的数组每个对象代表一个性能事件。entryTypes属性是需要观察的性能事件类型数组如mark,measure,paint,resource,longtask等。buffered: true选项可以在观察者创建时获取在观察者创建之前就已经记录的性能条目。2.4PerformanceEntry属性速查表属性类型说明namestring性能条目的名称。entryTypestring性能条目的类型如 mark, measure, paint 等。startTimenumber性能事件开始的时间戳以毫秒为单位相对于navigationStart。durationnumber性能事件的持续时间以毫秒为单位。detailanymark或measure中可选的详细信息。3. 精确测量 React 渲染阶段的挑战与策略React 的内部机制是高度优化的并且在不断演进。直接在 React 内部插入performance.mark并不总是可行或推荐的做法。我们需要利用 React 提供的 Hook 和生命周期回调巧妙地在用户代码层面捕获这些阶段的边界。3.1 挑战Render 阶段的边界函数组件的执行体就是 Render 阶段的主要部分。我们需要在函数开始和结束时打点。Pre-commit 阶段的边界这个阶段非常短且主要在 React 内部进行。useLayoutEffect的回调函数在 DOM 更新之前、浏览器绘制之前同步执行这使得它成为捕获 Pre-commit 阶段结束和 Commit 阶段开始的理想点。Commit 阶段的边界Commit 阶段包括 DOM 更新和执行副作用。useEffect的回调在 DOM 更新之后、浏览器绘制之后异步执行是捕获 Commit 阶段结束的合适时机。组件实例的唯一性同一个组件可能被多次渲染每个实例都需要独立的测量。清理在组件卸载时需要清除相关的性能标记避免内存泄漏。3.2 测量策略我们将采用自定义 HookuseComponentRenderProfiler来封装测量逻辑。Render 阶段 (render:start-render:end)render:start: 在函数组件体的最开始打点。render:end: 在函数组件体执行完毕后但在useLayoutEffect和useEffect之前这可以理解为函数组件逻辑计算完成的时刻。Pre-commit 阶段 (render:end-precommit:end)render:end: 作为 Pre-commit 阶段的起始点。precommit:end:useLayoutEffect的回调函数同步执行就在浏览器绘制之前。它的执行时机可以被视为 Pre-commit 阶段的结束以及 Commit 阶段实际 DOM 操作的开始。Commit 阶段 (precommit:end-commit:end)precommit:end: 作为 Commit 阶段的起始点。commit:end:useEffect的回调函数异步执行在 DOM 更新并浏览器绘制之后。它的执行时机可以被视为 Commit 阶段的结束。3.3 唯一标识符使用React.useId()React 18或一个自增的useRef计数器来为每个组件实例生成一个唯一的 ID确保性能标记的名称是唯一的。4. 实践构建useComponentRenderProfilerHook现在我们来编写这个核心的自定义 Hook。4.1useComponentRenderProfiler.tsimport { useEffect, useLayoutEffect, useRef, useId, useCallback } from react; /** * 一个用于测量 React 组件 Render, Pre-commit 和 Commit 阶段耗时的自定义 Hook。 * 它利用 Performance API 记录关键时间点并通过 PerformanceObserver 收集结果。 * * param componentName 组件的名称用于生成性能标记。 * param logToConsole 是否将测量结果打印到控制台。 */ function useComponentRenderProfiler(componentName: string, logToConsole: boolean true) { // 使用 useId 为每个组件实例生成一个唯一的 ID (React 18) // 如果是旧版本 React可以使用 useRef(0) 和一个全局自增计数器。 const instanceId useId(); const id ${componentName}-${instanceId}; // 使用 useRef 存储标记名称以便在 cleanup 时清除 const markNamesRef useRefstring[]([]); const measureNamesRef useRefstring[]([]); // 辅助函数用于记录 mark 并添加到 cleanup 列表 const recordMark useCallback((markName: string) { performance.mark(markName); markNamesRef.current.push(markName); }, []); // 辅助函数用于记录 measure 并添加到 cleanup 列表 const recordMeasure useCallback((measureName: string, startMark: string, endMark: string) { try { performance.measure(measureName, startMark, endMark); measureNamesRef.current.push(measureName); if (logToConsole) { // 获取最新测量结果并打印 const entry performance.getEntriesByName(measureName).pop(); if (entry) { console.log([Profiler] ${entry.name}: ${entry.duration.toFixed(2)}ms); } } } catch (error) { // 某些情况下如果标记不存在performance.measure 会抛出错误 // 例如在严格模式下双重渲染或者组件过早卸载 console.warn([Profiler] Failed to measure ${measureName}:, error); } }, [logToConsole]); // 1. Render 阶段开始 // 在组件函数体执行时触发因此不需要额外的 Hook // 每次组件渲染时这个 Hook 都会重新执行所以 render:start 会被记录 recordMark(${id}:render:start); // 2. Render 阶段结束 Pre-commit 阶段开始 // 在函数组件体执行完毕后但所有 Hooks 之前可以看作 Render 阶段的逻辑计算完成。 // 在此 Hook 内部我们可以在此处记录 render:end // 由于 recordMark 会在每次渲染时执行所以不需要单独的 Hook 来标记 render:end // 我们将 render:end 的标记逻辑放在 useLayoutEffect 之前确保它在 Pre-commit 开始前被记录 // 3. Pre-commit 阶段结束 Commit 阶段开始 // useLayoutEffect 在 DOM 更新前同步执行是 Pre-commit 阶段结束的理想点 useLayoutEffect(() { // 标记 Render 阶段结束 recordMark(${id}:render:end); recordMeasure(${id}:RenderDuration, ${id}:render:start, ${id}:render:end); // 标记 Pre-commit 阶段结束 (也是 Commit 阶段开始) recordMark(${id}:precommit:end); recordMeasure(${id}:PrecommitDuration, ${id}:render:end, ${id}:precommit:end); return () { // 在组件卸载或下一次 useLayoutEffect 运行前清除相关标记 // 注意这里只清除了 useLayoutEffect 内部创建的 mark // 外部的 mark 清理放在 useEffect 的 return 中 }; }, [id, recordMark, recordMeasure]); // 依赖项确保只在 id 变化时重新运行 // 4. Commit 阶段结束 // useEffect 在 DOM 更新并浏览器绘制后异步执行是 Commit 阶段结束的理想点 useEffect(() { recordMark(${id}:commit:end); recordMeasure(${id}:CommitDuration, ${id}:precommit:end, ${id}:commit:end); return () { // 清理所有为该组件实例创建的 mark 和 measure markNamesRef.current.forEach(mark performance.clearMarks(mark)); measureNamesRef.current.forEach(measure performance.clearMeasures(measure)); markNamesRef.current []; measureNamesRef.current []; }; }, [id, recordMark, recordMeasure]); } export default useComponentRenderProfiler;代码解析useId(): 为每个组件实例生成一个稳定的、唯一的 ID例如MyComponent-R1:0。这对于区分同一个组件在不同位置渲染的多个实例至关重要。markNamesRef,measureNamesRef: 使用useRef存储所有为当前组件实例创建的mark和measure的名称。这样在组件卸载时我们可以在useEffect的清理函数中清除这些性能条目防止浏览器性能缓冲区溢出或混淆。recordMark,recordMeasure: 封装了performance.mark和performance.measure同时将名称添加到ref中并可选地打印到控制台。recordMark(${id}:render:start): 位于函数组件的顶层每次组件函数执行即 Render 阶段开始时都会被调用。useLayoutEffect:在 DOM 更新前、浏览器绘制前同步执行。在这里标记render:end表示组件的纯渲染逻辑计算完毕。标记precommit:end表示 Pre-commit 阶段结束DOM 更新即将开始。测量RenderDuration和PrecommitDuration。useEffect:在 DOM 更新后、浏览器绘制后异步执行。在这里标记commit:end表示 Commit 阶段包括 DOM 更新和useLayoutEffect已完成。测量CommitDuration。其返回的清理函数会在组件卸载时或下一次useEffect运行前执行用于清除所有相关的mark和measure。为什么render:end放在useLayoutEffect内部理论上render:end应该在组件函数体执行完毕后立即标记。但在函数组件中函数体执行完毕后控制权会立即交给 React 内部的协调器。我们无法直接在函数组件的末尾插入一个同步的performance.mark因为它可能会被后续的 Hook 状态更新或 React 内部调度打断。将render:end放在useLayoutEffect的开始虽然略微滞后但仍然能准确地捕获到渲染逻辑完成的时机因为它是在浏览器绘制前、DOM 更新前同步执行的紧接着渲染逻辑完成。这是一种实用的妥协能够保持测量的相对准确性。5. 集成与使用在 React 组件中应用现在我们来看看如何在实际的 React 组件中使用这个 Hook。5.1MyComponent.tsximport React, { useState, useCallback, useMemo } from react; import useComponentRenderProfiler from ./useComponentRenderProfiler; interface MyComponentProps { data: number[]; isHighlighted: boolean; } const Item: React.FC{ value: number } React.memo(({ value }) { useComponentRenderProfiler(Item, false); // 子组件也进行性能分析但不在控制台打印由全局观察者处理 return liValue: {value}/li; }); const MyComponent: React.FCMyComponentProps ({ data, isHighlighted }) { useComponentRenderProfiler(MyComponent); // 在 MyComponent 上启用性能分析并打印到控制台 const [count, setCount] useState(0); const handleClick useCallback(() { setCount(prev prev 1); }, []); const expensiveCalculation useMemo(() { console.log(Performing expensive calculation...); let sum 0; for (let i 0; i 1000000; i) { sum Math.sqrt(i); } return sum; }, [count]); // 只有当 count 变化时才重新计算 return ( div style{{ padding: 20px, border: 2px solid ${isHighlighted ? red : blue} }} h2My Component ({id})/h2 pCount: {count}/p pExpensive Calculation Result: {expensiveCalculation.toFixed(2)}/p button onClick{handleClick}Increment Count/button ul {data.map((value, index) ( Item key{index} value{value} / ))} /ul /div ); }; export default MyComponent;5.2App.tsx(主应用文件)import React, { useState, useEffect } from react; import MyComponent from ./MyComponent; import { setupPerformanceObserver } from ./performanceObserverSetup; // 在应用入口点设置全局 PerformanceObserver // 确保只设置一次 useEffect(() { setupPerformanceObserver((entry) { // 在这里处理收集到的所有性能数据 // 可以发送到分析服务或在 DevTools 中查看 console.groupCollapsed(Performance Entry: ${entry.name}); console.log( Type: ${entry.entryType}); console.log( Duration: ${entry.duration.toFixed(2)}ms); console.log( Start Time: ${entry.startTime.toFixed(2)}ms); console.groupEnd(); }); }, []); const App: React.FC () { const [data, setData] useState([1, 2, 3, 4, 5]); const [highlight, setHighlight] useState(false); useEffect(() { const interval setInterval(() { // 模拟数据更新 setData(prevData [...prevData.slice(1), prevData[0] 5]); setHighlight(prev !prev); }, 2000); return () clearInterval(interval); }, []); return ( div h1React Performance Profiling Example/h1 MyComponent data{data} isHighlighted{highlight} / MyComponent data{[10, 20]} isHighlighted{!highlight} / {/* 另一个实例 */} /div ); }; export default App;5.3performanceObserverSetup.ts(全局 PerformanceObserver 配置)// performanceObserverSetup.ts let performanceObserver: PerformanceObserver | null null; /** * 设置一个全局 PerformanceObserver 来监听性能事件。 * 这个函数应该只被调用一次。 * param onEntryCallback 当有新的性能条目可用时调用的回调函数。 */ export function setupPerformanceObserver(onEntryCallback: (entry: PerformanceEntry) void) { if (performanceObserver) { console.warn(PerformanceObserver has already been set up.); return; } performanceObserver new PerformanceObserver((list) { list.getEntries().forEach(entry { if (entry.entryType measure) { onEntryCallback(entry); // 每次处理完后清除 measure防止缓冲区过大 performance.clearMeasures(entry.name); } // 对于 mark由组件内部的 useEffect cleanup 清理 }); }); // 监听 mark 和 measure 类型的性能事件 // buffered: true 意味着观察者创建时会接收到之前记录的所有性能条目 performanceObserver.observe({ entryTypes: [mark, measure], buffered: true }); console.log(PerformanceObserver initialized.); } /** * 断开 PerformanceObserver。 */ export function disconnectPerformanceObserver() { if (performanceObserver) { performanceObserver.disconnect(); performanceObserver null; console.log(PerformanceObserver disconnected.); } }通过这种方式我们可以在浏览器控制台或 DevTools 的 Performance 面板中看到详细的性能数据。特别是通过全局的PerformanceObserver我们可以收集到所有组件实例的性能数据并进行统一的分析。6. 高级考虑与最佳实践6.1React.Profilervs.Performance API这是一个常见的疑问。React.Profiler是 React 提供的另一个性能分析工具它通过onRender回调报告组件树中哪些部分发生了渲染以及渲染的总耗时。React.Profiler优点高层级视图易于识别哪些组件渲染了、渲染了多久以及渲染的原因actualDurationvsbaseDuration。它提供了组件树级别的洞察。缺点无法直接测量 React 内部的 Render、Pre-commit、Commit阶段的精确耗时。它报告的是组件从开始渲染到完成 DOM 更新或useLayoutEffect执行完毕的总时间。Performance API 自定义 Hook优点提供了对 React 内部 Render、Pre-commit、Commit 阶段的精细测量帮助我们理解在每个阶段具体花费了多少时间。缺点需要在每个需要测量的组件中手动引入 Hook并且需要自己解析和聚合数据。两者是互补的。React.Profiler可以帮助你找到“哪些组件渲染慢”而Performance API的方法可以帮助你找到“这个慢组件在哪个阶段慢了以及为什么”。6.2 生产环境中的应用条件启用在生产环境中应只在需要时例如通过 URL 参数、特定的用户组或 A/B 测试才启用性能分析因为过多的mark/measure会带来轻微的性能开销和数据传输成本。数据采样不要为每个用户、每次渲染都发送数据。可以对用户进行采样或对数据进行聚合例如每隔 N 次渲染发送一次平均值。数据脱敏如果性能数据包含敏感信息例如组件名称可能暴露业务逻辑需要进行脱敏处理。后端集成将收集到的性能数据发送到后端分析服务如 Prometheus, Grafana, Datadog 或自定义的 APM进行存储、可视化和告警。6.3StrictMode的影响在React.StrictMode模式下React 会有意地双重调用某些函数如函数组件体、useEffect的清理函数以帮助开发者发现潜在的副作用问题。这会导致useComponentRenderProfiler中的render:start和其他标记被记录两次。解决方案在开发环境调试时可以暂时关闭StrictMode。在处理性能数据时需要考虑到StrictMode的影响可能会看到重复的测量结果。通常我们关注的是实际渲染的最后一次有效测量。6.4 解释测量数据RenderDuration 过高通常意味着组件内部进行了复杂的计算、大数据遍历或创建了大量的 React 元素。优化方向使用useMemo、useCallback缓存计算结果和回调函数使用React.memo避免不必要的子组件渲染数据结构优化。PrecommitDuration 过高这个阶段通常非常短。如果它显著地高可能意味着getSnapshotBeforeUpdate中有耗时操作对于类组件或者在处理ref时有复杂逻辑。CommitDuration 过高DOM 操作过多组件渲染导致了大量的 DOM 节点创建、更新或删除。useLayoutEffect耗时如果useLayoutEffect中有复杂的 DOM 测量或修改逻辑会阻塞浏览器绘制。useEffect耗时如果useEffect中有大量耗时操作即使是异步的也可能在首次执行时影响感知性能。优化方向虚拟化/窗口化长列表减少 DOM 节点数量将useLayoutEffect中非关键的逻辑移到useEffect中优化副作用逻辑。6.5 局限性测量精度尽管Performance API精度很高但由于 JavaScript 单线程的特性以及浏览器内部的调度和优化测量结果可能会受到其他任务、垃圾回收等因素的轻微影响。React 内部调度React 18 引入的并发模式Concurrent Mode和 Suspense 允许 React 中断和恢复渲染。这使得精确地定义和测量 Render 阶段变得更加复杂因为一个 Render 阶段可能被分成多个时间片执行。本文的 Hook 在当前宏任务内的测量是准确的但如果渲染被多次中断可能需要更复杂的逻辑来关联这些中断的片段。浏览器差异不同浏览器对Performance API的实现和行为可能存在细微差异。7. 结语通过Performance API结合自定义 React Hook我们能够对组件在 Render、Pre-commit 和 Commit 阶段的耗时进行精细化测量。这种深入的洞察力是识别性能瓶颈、进行有针对性优化的重要基础。理解 React 的渲染机制并善用浏览器提供的强大工具是每一位 React 开发者迈向卓越的关键一步。希望今天的讲座能为大家在性能优化之路上提供新的思路和工具。
版权声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

营销网站找什么公司做wordpress签到功能

文章目录为什么使用文件什么是文件文件名程序文件数据文件文件的打开和关闭流标准流文件指针文件的打开和关闭文件的随机读写文件读取结束的判定feof与ferror文件缓冲区为什么使用文件 如果没有文件操作,程序运行时的数据存储在内存中,当程序运行结束时…

张小明 2025/12/25 5:17:38 网站建设

wap网站技术百度账号快速注册入口

离Deadline还剩6小时。你的论文内容早已打磨完美,却还卡在最后一步——按照期刊要求,逐条调整50条参考文献的格式:英文作者名该缩写还是全拼?期刊名用斜体还是缩写?DOI链接的格式是什么?你感到一阵荒谬&…

张小明 2025/12/25 5:15:37 网站建设

安徽省建设工程资料上传网站做网站要多少的分辨率

STM32F103C8T6微控制器实战指南:从选型到项目开发全解析 【免费下载链接】STM32F103C8T6中文数据手册 本资源文件提供了STM32F103C8T6微控制器的中文数据手册。STM32F103C8T6是一款基于ARM Cortex-M3内核的32位微控制器,具有高性能、低功耗和低电压特性&…

张小明 2025/12/25 5:13:35 网站建设

建设银行河北省分行官方网站网站移动终端建设

基于MATLAB的单闭环直流调速系统设计 本设计包括设计报告,仿真电气接线图。 设计要求 (1)该调速系统能进行平滑的速度调节,负载电机不可逆运行,具有较宽的调速范围(D≥10),系统在工作…

张小明 2025/12/25 5:11:34 网站建设

莆田网站自助建站配置安装环境 wordpress 阿里云

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容: 创建一个对比演示项目,分别用原生HTML表格、主流UI框架表格组件和ag-Grid实现相同的企业级数据表格功能,包括:1.10万条数据渲染;2.复…

张小明 2025/12/25 5:09:33 网站建设

做网站是干嘛的保健品网站建设案例

DL00358-基于迁移学习的离心泵滚动轴承故障自动识别方法研究 在输入原始时序加速度数据的网络中,基于与输入经过特征提取的数据的网络同样的考虑,取batch_size20(由于原始时序数据长度太长,因此需要对原始数据进行截取。 所用的数…

张小明 2025/12/25 5:07:31 网站建设