Vue 3 + TypeScript 严格模式下的 Performance.now() 实践:构建高性能前端应用

在 Vue 3 的响应式系统与 Composition API 加持下,结合 TypeScript 严格模式的类型安全特性,我们可以构建出既精确又健壮的浏览器性能监控体系。本文将深入探讨在 Vue 3 生态中使用 performance.now() 的典型场景与最佳实践。

一、严格模式与 Vue 3 的完美结合

1.1 TypeScript 配置

复制代码
{
  "compilerOptions": {
    "strict": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "strictFunctionTypes": true
  }
}

1.2 Vue 3 类型增强

TypeScript 复制代码
// shims-vue.d.ts
declare module 'vue-router' {
  interface RouteMeta {
    // 为路由元信息添加性能监控配置
    perfMark?: string;
    enableTrack?: boolean;
  }
}

// performance.d.ts
interface Window {
  performance: Performance & {
    // 扩展类型定义以支持新 API
    measureUserAgentSpecificMemory?: () => Promise<any>;
  };
}

场景一:组件生命周期性能追踪

2.1 通用性能监控 Hook

TypeScript 复制代码
import { onMounted, onUpdated, onUnmounted, ref } from 'vue';

interface PerformanceMetrics {
  mountTime: number | null;
  updateTime: number | null;
  unmountTime: number | null;
}

/**
 * 严格模式:组件生命周期耗时监控 Composable
 * @param componentName 组件名称
 * @returns 响应式性能指标
 */
export function useComponentPerformance(componentName: string) {
  const metrics = ref<PerformanceMetrics>({
    mountTime: null,
    updateTime: null,
    unmountTime: null
  });

  // 类型守卫:确保性能 API 可用
  const isPerformanceSupported = (): boolean => {
    return typeof performance !== 'undefined' && 
           typeof performance.now === 'function' &&
           typeof performance.mark === 'function';
  };

  // 严格模式:处理可能为 null 的情况
  const safeNow = (): number => {
    return isPerformanceSupported() ? performance.now() : Date.now();
  };

  onMounted(() => {
    const startTime = safeNow();
    
    // 在下一个微任务中测量,确保 DOM 渲染完成
    Promise.resolve().then(() => {
      metrics.value.mountTime = safeNow() - startTime;
      
      if (metrics.value.mountTime > 100) {
        console.warn(`[${componentName}] 挂载耗时超过100ms: ${metrics.value.mountTime.toFixed(2)}ms`);
      }

      // 上报监控系统
      reportToAnalytics({
        type: 'vue-lifecycle',
        component: componentName,
        phase: 'mount',
        duration: metrics.value.mountTime,
        timestamp: safeNow()
      });
    });
  });

  let updateStartTime: number | null = null;

  onUpdated(() => {
    if (updateStartTime === null) {
      updateStartTime = safeNow();
    }

    // 防抖:避免频繁更新
    Promise.resolve().then(() => {
      if (updateStartTime !== null) {
        metrics.value.updateTime = safeNow() - updateStartTime;
        updateStartTime = null;

        if (metrics.value.updateTime > 50) {
          console.warn(`[${componentName}] 更新耗时超过50ms: ${metrics.value.updateTime.toFixed(2)}ms`);
        }
      }
    });
  });

  onUnmounted(() => {
    const startTime = safeNow();
    
    // Vue 3 中 onUnmounted 同步执行
    metrics.value.unmountTime = safeNow() - startTime;
    
    // 清理性能标记
    if (isPerformanceSupported()) {
      performance.clearMarks();
      performance.clearMeasures();
    }
  });

  return { metrics };
}

// 组件中使用
<script setup lang="ts">
import { ref } from 'vue';
import { useComponentPerformance } from '@/composables/usePerformance';

const props = defineProps<{ userId: string }>();
const userData = ref(null);

// 启用性能监控
const { metrics } = useComponentPerformance('UserProfile');

// 模拟数据加载
const loadUser = async () => {
  const start = performance.now();
  const response = await fetch(`/api/users/${props.userId}`);
  userData.value = await response.json();
  console.log(`数据加载耗时: ${performance.now() - start}ms`);
};
</script>

场景二:异步操作与请求性能追踪

3.1 基于 Pinia 的全局请求性能监控

TypeScript 复制代码
// stores/performance.ts
import { defineStore } from 'pinia';
import type { Router } from 'vue-router';

export interface RequestMetrics {
  url: string;
  method: string;
  duration: number;
  status: number;
  timestamp: number;
  phase: 'dns' | 'tcp' | 'ttfb' | 'download';
}

export const usePerformanceStore = defineStore('performance', () => {
  const requestMetrics = ref<RequestMetrics[]>([]);
  const slowRequests = computed(() => 
    requestMetrics.value.filter(r => r.duration > 1000)
  );

  /**
   * 严格模式:添加请求指标
   */
  function addRequestMetric(metric: RequestMetrics): void {
    requestMetrics.value.push(metric);
    
    // 限制数组大小,避免内存泄漏
    if (requestMetrics.value.length > 100) {
      requestMetrics.value.shift();
    }
  }

  // 与 Vue Router 集成,自动追踪路由切换
  function trackRouteChanges(router: Router): void {
    let startTime: number | null = null;

    router.beforeEach((to, from, next) => {
      // 严格模式:检查是否启用性能追踪
      if (to.meta.enableTrack !== false) {
        startTime = performance.now();
      }
      next();
    });

    router.afterEach((to) => {
      if (startTime !== null && to.meta.enableTrack !== false) {
        const duration = performance.now() - startTime;
        
        addRequestMetric({
          url: to.fullPath,
          method: 'ROUTE_CHANGE',
          duration,
          status: 200,
          timestamp: performance.now(),
          phase: 'download'
        });

        // 慢路由警告
        if (duration > 500) {
          console.warn(`[Slow Route] ${to.path}: ${duration.toFixed(2)}ms`);
        }
        
        startTime = null;
      }
    });
  }

  return {
    requestMetrics,
    slowRequests,
    addRequestMetric,
    trackRouteChanges
  };
});

// 在 main.ts 中初始化
const performanceStore = usePerformanceStore(router);
performanceStore.trackRouteChanges(router);

3.2 请求拦截器集成

TypeScript 复制代码
// utils/http-client.ts
import axios, { type AxiosInstance } from 'axios';
import { usePerformanceStore } from '@/stores/performance';

export function createHttpClient(): AxiosInstance {
  const client = axios.create({
    baseURL: import.meta.env.VITE_API_BASE_URL,
    timeout: 10000
  });

  // 严格模式:为 config 添加类型
  client.interceptors.request.use((config) => {
    // 附加时间戳到 config
    (config as any).__startTime = performance.now();
    return config;
  });

  client.interceptors.response.use(
    (response) => {
      const duration = performance.now() - (response.config as any).__startTime;
      
      // 严格模式:确保 store 已初始化
      const performanceStore = usePerformanceStore();
      performanceStore.addRequestMetric({
        url: response.config.url || '',
        method: response.config.method?.toUpperCase() || 'GET',
        duration,
        status: response.status,
        timestamp: performance.now(),
        phase: 'download'
      });

      return response;
    },
    (error) => {
      const duration = performance.now() - (error.config as any).__startTime;
      
      const performanceStore = usePerformanceStore();
      performanceStore.addRequestMetric({
        url: error.config?.url || '',
        method: error.config?.method?.toUpperCase() || 'GET',
        duration,
        status: error.response?.status || 0,
        timestamp: performance.now(),
        phase: 'download'
      });

      return Promise.reject(error);
    }
  );

  return client;
}

场景三:动画与交互性能监控

4.1 基于 performance.now() 的 FPS 监控

TypeScript 复制代码
// composables/useFPSMonitor.ts
import { ref, onUnmounted, type Ref } from 'vue';

interface FPSMetrics {
  current: number;
  min: number;
  max: number;
  average: number;
  frameCount: number;
}

export function useFPSMonitor(): Ref<FPSMetrics> {
  const metrics = ref<FPSMetrics>({
    current: 0,
    min: 144,
    max: 0,
    average: 0,
    frameCount: 0
  });

  let isRunning = false;
  let lastTime = 0;
  let frameCount = 0;
  let rafId: number | null = null;
  const fpsHistory: number[] = [];

  const isPerformanceSupported = (): boolean => {
    return typeof performance !== 'undefined' && 
           typeof performance.now === 'function';
  };

  const tick = (currentTime: number) => {
    if (!isRunning) return;

    frameCount++;

    // 严格模式:必须检查 lastTime 是否有效
    if (lastTime > 0) {
      const deltaTime = currentTime - lastTime;
      const fps = Math.round(1000 / deltaTime);

      // 更新统计
      fpsHistory.push(fps);
      if (fpsHistory.length > 60) {
        fpsHistory.shift();
      }

      // 严格模式:处理空数组
      const average = fpsHistory.length > 0 
        ? fpsHistory.reduce((a, b) => a + b, 0) / fpsHistory.length 
        : 0;

      metrics.value = {
        current: fps,
        min: Math.min(...fpsHistory, metrics.value.min),
        max: Math.max(...fpsHistory, metrics.value.max),
        average: Math.round(average),
        frameCount
      };

      // 性能警告
      if (fps < 30) {
        console.warn(`[FPS Warning] 帧率过低: ${fps} FPS`);
      }
    }

    lastTime = currentTime;
    rafId = requestAnimationFrame(tick);
  };

  const start = () => {
    if (!isPerformanceSupported()) {
      console.warn('Performance API not available');
      return;
    }

    isRunning = true;
    lastTime = performance.now();
    rafId = requestAnimationFrame(tick);
  };

  const stop = () => {
    isRunning = false;
    if (rafId !== null) {
      cancelAnimationFrame(rafId);
    }
  };

  // 自动启动
  start();

  // 组件卸载时清理
  onUnmounted(() => {
    stop();
  });

  return metrics;
}

// 在组件中使用
<template>
  <div class="fps-counter" v-if="showFPS">
    FPS: {{ fpsMetrics.current }} 
    (Avg: {{ fpsMetrics.average }})
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useFPSMonitor } from '@/composables/useFPSMonitor';

const showFPS = ref(import.meta.env.DEV); // 仅在开发环境显示
const fpsMetrics = useFPSMonitor();
</script>

4.2 滚动性能监控

TypeScript 复制代码
// composables/useScrollPerformance.ts
import { ref, onMounted, onUnmounted } from 'vue';

export interface ScrollMetrics {
  scrollCount: number;
  averageDelay: number;
  maxDelay: number;
  isJanky: boolean;
}

export function useScrollPerformance(element?: HTMLElement) {
  const metrics = ref<ScrollMetrics>({
    scrollCount: 0,
    averageDelay: 0,
    maxDelay: 0,
    isJanky: false
  });

  let lastScrollTime = 0;
  let delays: number[] = [];
  let rafId: number | null = null;

  const isPerformanceSupported = () => 
    typeof performance !== 'undefined' && typeof performance.now === 'function';

  const handleScroll = () => {
    if (!isPerformanceSupported()) return;

    const currentTime = performance.now();
    
    // 使用 requestAnimationFrame 测量延迟
    if (rafId !== null) {
      cancelAnimationFrame(rafId);
    }

    rafId = requestAnimationFrame(() => {
      const delay = performance.now() - currentTime;
      delays.push(delay);
      
      // 严格模式:限制数组大小
      if (delays.length > 100) {
        delays.shift();
      }

      // 计算统计
      const average = delays.reduce((a, b) => a + b, 0) / delays.length;
      const max = Math.max(...delays);

      metrics.value = {
        scrollCount: metrics.value.scrollCount + 1,
        averageDelay: Math.round(average),
        maxDelay: Math.round(max),
        isJanky: average > 16.67 // 超过 60fps 阈值
      };

      if (average > 16.67) {
        console.warn(`[Scroll Jank] 滚动延迟过高: ${average.toFixed(2)}ms`);
      }
    });
  };

  onMounted(() => {
    const target = element || window;
    target.addEventListener('scroll', handleScroll, { passive: true });
  });

  onUnmounted(() => {
    const target = element || window;
    target.removeEventListener('scroll', handleScroll);
    if (rafId !== null) {
      cancelAnimationFrame(rafId);
    }
  });

  return { metrics };
}

场景四:复杂计算任务性能隔离

5.1 Web Worker 性能监控

TypeScript 复制代码
// composables/useWorkerPerformance.ts
import { ref, onUnmounted, type Ref } from 'vue';

export interface WorkerMetrics {
  taskCount: number;
  totalTime: number;
  averageTime: number;
  lastTaskDuration: number | null;
}

export interface WorkerTask<T = any> {
  id: string;
  payload: T;
}

export function useWorkerPerformance(workerPath: string) {
  const metrics: Ref<WorkerMetrics> = ref({
    taskCount: 0,
    totalTime: 0,
    averageTime: 0,
    lastTaskDuration: null
  });

  let worker: Worker | null = null;

  const isPerformanceSupported = () => 
    typeof performance !== 'undefined' && typeof performance.now === 'function';

  const executeTask = <T, R>(task: WorkerTask<T>): Promise<R> => {
    return new Promise((resolve, reject) => {
      if (!worker) {
        reject(new Error('Worker not initialized'));
        return;
      }

      const startTime = isPerformanceSupported() ? performance.now() : Date.now();

      const handleMessage = (event: MessageEvent) => {
        const duration = isPerformanceSupported() 
          ? performance.now() - startTime 
          : Date.now() - startTime;

        // 更新指标
        metrics.value.taskCount++;
        metrics.value.totalTime += duration;
        metrics.value.averageTime = metrics.value.totalTime / metrics.value.taskCount;
        metrics.value.lastTaskDuration = duration;

        // 性能警告
        if (duration > 1000) {
          console.warn(`Worker 任务耗时过长: ${duration.toFixed(2)}ms`);
        }

        resolve(event.data);
      };

      const handleError = (error: ErrorEvent) => {
        reject(new Error(`Worker error: ${error.message}`));
      };

      worker.addEventListener('message', handleMessage, { once: true });
      worker.addEventListener('error', handleError, { once: true });

      worker.postMessage(task);
    });
  };

  // 严格模式:初始化时检查环境
  if (window.Worker && isPerformanceSupported()) {
    worker = new Worker(workerPath);
  } else {
    console.warn('Web Worker or Performance API not supported');
  }

  onUnmounted(() => {
    if (worker) {
      worker.terminate();
    }
  });

  return {
    metrics: readonly(metrics), // 严格模式:防止外部修改
    executeTask,
    isSupported: worker !== null
  };
}

// Worker 脚本 (worker.ts)
import type { WorkerTask } from '@/composables/useWorkerPerformance';

self.addEventListener('message', (event: MessageEvent<WorkerTask>) => {
  const { id, payload } = event.data;
  
  // 模拟复杂计算
  const result = heavyComputation(payload);
  
  self.postMessage({ id, result });
});

function heavyComputation(data: any): any {
  // 实际计算逻辑
  return data;
}

5.2 在 Vue 组件中使用

TypeScript 复制代码
<template>
  <div>
    <canvas ref="canvas"></canvas>
    <div v-if="workerPerf.isSupported">
      <p>任务数: {{ workerPerf.metrics.taskCount }}</p>
      <p>平均耗时: {{ workerPerf.metrics.averageTime.toFixed(2) }}ms</p>
      <p v-if="workerPerf.metrics.lastTaskDuration">
        上次任务: {{ workerPerf.metrics.lastTaskDuration.toFixed(2) }}ms
      </p>
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue';
import { useWorkerPerformance } from '@/composables/useWorkerPerformance';

const workerPerf = useWorkerPerformance('/workers/image-processor.js');

onMounted(async () => {
  if (!workerPerf.isSupported) return;

  const result = await workerPerf.executeTask({
    id: 'render-1',
    payload: { width: 1920, height: 1080 }
  });

  console.log('Worker 任务完成:', result);
});
</script>

场景五:自定义指令性能监控

6.1 v-perf 指令

TypeScript 复制代码
// directives/vPerf.ts
import type { Directive } from 'vue';

interface PerfBinding {
  name: string;
  report?: boolean;
  threshold?: number;
}

export const vPerf: Directive<HTMLElement, PerfBinding> = {
  mounted(el, binding) {
    const { name, report = true, threshold = 100 } = binding.value;
    
    // 严格模式:检查性能 API
    if (typeof performance === 'undefined') return;

    const startTime = performance.now();
    
    // 在下一个微任务中测量
    Promise.resolve().then(() => {
      const duration = performance.now() - startTime;
      
      // 严格模式:阈值必须为数字
      if (duration > (threshold as number)) {
        console.warn(`[v-perf] ${name} 渲染耗时: ${duration.toFixed(2)}ms`);
        
        if (report) {
          // 上报到监控系统
          reportToAnalytics({
            type: 'directive-perf',
            name,
            duration,
            timestamp: performance.now()
          });
        }
      }
    });
  }
};

// 在 main.ts 中注册
app.directive('perf', vPerf);

// 组件中使用
<template>
  <div v-perf="{ name: 'ComplexChart', threshold: 50 }">
    <canvas ref="chartCanvas"></canvas>
  </div>
</template>

最佳实践总结

1. 集中式 Performance API 检查

TypeScript 复制代码
// utils/performance-guard.ts
export function assertPerformanceAPI(): asserts performance is Performance {
  if (typeof performance === 'undefined') {
    throw new Error('Performance API is not supported in this environment');
  }
}

// 在 Composable 中使用
export function usePreciseTimer() {
  try {
    assertPerformanceAPI();
    // TypeScript 现在知道 performance 一定存在
    const start = performance.now();
    return {
      end: () => performance.now() - start
    };
  } catch {
    // 降级方案
    const start = Date.now();
    return {
      end: () => Date.now() - start
    };
  }
}

2. Vue 3 与 PerformanceObserver 集成

TypeScript 复制代码
// composables/useWebVitals.ts
import { onMounted, onUnmounted } from 'vue';

export function useWebVitals() {
  onMounted(() => {
    if ('PerformanceObserver' in window) {
      // LCP
      const lcpObserver = new PerformanceObserver((list) => {
        const entries = list.getEntries();
        const lastEntry = entries[entries.length - 1];
        
        if (lastEntry) {
          console.log('LCP:', lastEntry.startTime);
          // 上报数据...
        }
      });
      
      lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });
      
      onUnmounted(() => {
        lcpObserver.disconnect();
      });
    }
  });
}

3. 性能数据持久化与可视化

TypeScript 复制代码
// stores/performanceDashboard.ts
export const usePerformanceDashboard = defineStore('performance-dashboard', () => {
  const realtimeMetrics = ref<PerformanceMetrics[]>([]);
  
  // 使用 BroadcastChannel 跨标签页同步
  const bc = new BroadcastChannel('perf-channel');
  
  bc.onmessage = (event) => {
    const metric = event.data as PerformanceMetrics;
    realtimeMetrics.value.push(metric);
    
    // 严格模式:限制数据量
    if (realtimeMetrics.value.length > 1000) {
      realtimeMetrics.value = realtimeMetrics.value.slice(-500);
    }
  };

  return {
    realtimeMetrics
  };
});

性能与精度考量

  1. 采样率控制:生产环境建议 10% 采样,避免影响用户体验

  2. 非阻塞上报 :使用 requestIdleCallback 延迟上报

  3. 内存管理:及时清理 performance entry,避免内存泄漏

TypeScript 复制代码
// 严格模式:确保清理函数存在
if (performance.clearResourceTimings) {
  performance.clearResourceTimings();
}

通过 Vue 3 的响应式系统与 TypeScript 严格模式的结合,我们不仅获得了精确的性能测量能力,还确保了代码的健壮性和可维护性。这种"监控即代码"的实践,让性能优化成为开发流程的自然组成部分。

相关推荐
王林不想说话3 小时前
受控/非受控组件分析
前端·react.js·typescript
by__csdn9 小时前
大前端:定义、演进与实践全景解析
前端·javascript·vue.js·react.js·typescript·ecmascript·动画
by__csdn12 小时前
JavaScript性能优化实战:异步与延迟加载全方位攻略
开发语言·前端·javascript·vue.js·react.js·typescript·ecmascript
doupoa12 小时前
VitePressv2.0 + TailwindCSSv4.1 集成方案
typescript·前端框架·json·html5·postcss
by__csdn13 小时前
javascript 性能优化实战:异步和延迟加载
开发语言·前端·javascript·vue.js·性能优化·typescript·ecmascript
by__csdn13 小时前
JavaScript性能优化实战:减少DOM操作全方位攻略
前端·javascript·vue.js·react.js·性能优化·typescript
一只一只妖13 小时前
uni-app + ts请求封装最佳实践(GET/POST + 加载态 + 错误兜底)
typescript·uni-app
ttod_qzstudio1 天前
深入理解 TypeScript 数组的 find 与 filter 方法:精准查找的艺术
javascript·typescript·filter·find
锦瑟弦音1 天前
Luban + Cocos3.8.7 + Typescript + Json
笔记·游戏·typescript