【AI 编程实战】第 11 篇:让小程序飞起来 - 性能优化实战指南

功能都做完了,但小程序体验评分只有 70 分?首次打开要等 3 秒?这篇文章以心动恋聊 小程序为例,展示如何和 AI 对话,系统性地进行分包优化、图片优化、渲染优化,让小程序飞起来。

系列专栏【AI 编程实战】专栏目录

本篇主题:让小程序飞起来 - 性能优化实战指南

实战项目:心动恋聊 - AI 恋爱聊天助手

一、开篇:性能问题的发现

1.1 体验评分告警

txt 复制代码
我:小程序提审时,体验评分只有 70 分。
    主要问题:
    - 代码包体积过大(2.1MB)
    - 首屏渲染时间过长(2.8s)
    - 图片资源未优化

    能帮我系统性地优化一下吗?

1.2 AI 给出优化路线图

txt 复制代码
AI:性能优化需要系统性进行,我给你一个完整的路线图:

📊 性能指标目标:
- 主包体积 < 2MB(当前 2.1MB)
- 首屏加载 < 1.5s(当前 2.8s)
- 体验评分 > 90 分(当前 70 分)

📋 优化路线:

1. 分包优化
   - 主包只放核心页面
   - 非核心功能放分包
   - 配置分包预加载

2. 构建优化
   - 使用 bundle-optimizer 插件
   - 配置按需编译
   - 移除未使用的依赖

3. 图片优化
   - 压缩图片资源
   - 使用 CDN 加载
   - 实现懒加载

4. 渲染优化
   - 减少首屏渲染节点
   - 合理使用 computed
   - 避免不必要的响应式

5. 网络优化
   - 请求合并
   - 数据缓存
   - 预加载策略

我们按这个顺序来,先从分包开始。

二、分包优化:减少主包体积

2.1 我的需求

txt 复制代码
我:主包体积超了,有哪些页面可以放到分包里?

2.2 AI 分析页面结构

txt 复制代码
AI:分析你的 pages.json,建议这样分包:

📊 页面分析:

【主包页面】(必须放主包)
- pages/index/index        → 首页,TabBar 页面
- pages/chase/chase        → 帮你追,TabBar 页面
- pages/my/my              → 我的,TabBar 页面
- pages/login/index        → 登录页,高频访问

【可分包页面】
- pages/feedback/index     → 反馈页,低频
- pages/plan-result/*      → 计划结果,二级页面
- pages/plan-continue/*    → 计划续期,二级页面

【建议分包结构】
subPackages/
├── agreement/     → 协议页面(用户协议、隐私政策)
├── vip/           → VIP 相关页面
└── planForm/      → 计划表单页面

📌 分包原则:
1. TabBar 页面必须在主包
2. 高频访问页面放主包
3. 低频、大体积页面放分包
4. 相关功能聚合到同一分包

2.3 分包配置

json 复制代码
// pages.json
{
  "pages": [
    // 主包:核心 TabBar 页面
    { "path": "pages/index/index", "type": "home" },
    { "path": "pages/chase/chase" },
    { "path": "pages/my/my" },
    { "path": "pages/login/index" }
  ],
  "subPackages": [
    // 分包1:协议相关
    {
      "root": "subPackages/agreement",
      "pages": [
        { "path": "pages/agreement/user" },
        { "path": "pages/agreement/privacy" },
        { "path": "pages/agreement/member" }
      ]
    },
    // 分包2:VIP 功能
    {
      "root": "subPackages/vip",
      "pages": [
        {
          "path": "pages/index/index",
          "style": {
            "navigationStyle": "custom",
            "navigationBarTitleText": "VIP会员"
          }
        }
      ]
    },
    // 分包3:计划表单
    {
      "root": "subPackages/planForm",
      "pages": [
        {
          "path": "pages/index/index",
          "style": {
            "navigationStyle": "custom",
            "navigationBarTitleText": "定制追求计划"
          }
        }
      ]
    }
  ]
}

2.4 分包预加载

txt 复制代码
我:用户从首页跳转 VIP 页面,能不能提前加载分包?
txt 复制代码
AI:可以配置分包预加载,在用户访问主包页面时,
静默下载可能用到的分包:
json 复制代码
// pages.json
{
  "preloadRule": {
    // 访问首页时,预加载 VIP 分包
    "pages/index/index": {
      "network": "all",
      "packages": ["subPackages/vip"]
    },
    // 访问"我的"页面时,预加载协议分包
    "pages/my/my": {
      "network": "wifi",
      "packages": ["subPackages/agreement"]
    }
  }
}
txt 复制代码
AI:预加载配置说明:

📊 network 选项:
- "all":所有网络环境都预加载
- "wifi":仅 WiFi 环境预加载

📌 预加载策略:
1. VIP 页面用户可能随时访问,用 "all"
2. 协议页面不紧急,用 "wifi" 节省流量
3. 不要预加载太多分包,反而影响首屏

三、构建优化:使用 Bundle Optimizer

3.1 我的需求

txt 复制代码
我:听说有个 bundle-optimizer 插件可以优化分包,
    能帮我配置一下吗?

3.2 AI 配置优化插件

txt 复制代码
AI:@uni-ku/bundle-optimizer 可以做以下优化:

🔧 插件功能:
1. 分包优化:自动分析依赖,减少重复
2. 异步跨包调用:模块按需加载
3. 异步组件引用:组件懒加载

我来帮你配置:
typescript 复制代码
// vite.config.ts
import Optimization from '@uni-ku/bundle-optimizer';

export default ({ mode }) => {
  const { UNI_PLATFORM } = process.env;

  return defineConfig({
    plugins: [
      // ... 其他插件

      // 分包优化插件(App 端不支持)
      UNI_PLATFORM !== 'app' && UNI_PLATFORM !== 'app-plus' && Optimization({
        enable: {
          optimization: true,        // 启用分包优化
          'async-import': true,      // 启用异步导入
          'async-component': true,   // 启用异步组件
        },
        dts: {
          base: 'src/types',         // 类型声明目录
        },
        logger: false,               // 关闭日志
      }),
    ],
  });
};

3.3 我追问优化效果

txt 复制代码
我:这个插件具体优化了什么?
txt 复制代码
AI:主要优化三个方面:

📊 1. 分包依赖优化(optimization)
- 分析主包和分包的依赖关系
- 公共依赖提取到主包
- 分包独有依赖留在分包

【优化前】
主包:vue + lodash + dayjs + moment
分包A:moment(重复)
分包B:lodash(重复)

【优化后】
主包:vue + lodash + dayjs + moment(只打包一次)
分包A:无重复
分包B:无重复

📊 2. 异步导入(async-import)
// 优化前:同步导入,增加主包体积
import { heavyUtils } from '@/utils/heavy'

// 优化后:异步导入,按需加载
const { heavyUtils } = await import('@/utils/heavy')

📊 3. 异步组件(async-component)
// 优化前:组件全量打包
import HeavyComponent from '@/components/HeavyComponent.vue'

// 优化后:组件懒加载
const HeavyComponent = defineAsyncComponent(
  () => import('@/components/HeavyComponent.vue')
)

📌 优化效果:
- 主包体积减少 15-30%
- 分包体积减少 10-20%
- 首屏加载时间减少 20-40%

四、图片优化:从源头减负

4.1 我的需求

txt 复制代码
我:项目里有很多图片,如何优化?

4.2 AI 给出图片优化策略

txt 复制代码
AI:图片优化分几个层面:

📊 图片优化策略:

1. 压缩图片(构建时)
   - PNG/JPG 压缩
   - 转 WebP 格式(兼容性好的平台)

2. 图片懒加载(运行时)
   - 首屏图片立即加载
   - 非首屏图片滚动时加载

3. CDN 加速(网络层)
   - 静态资源上 CDN
   - 图片裁剪服务

4. 占位图处理(体验层)
   - 加载中显示骨架屏
   - 加载失败显示默认图

我来给你演示实际代码:

4.3 图片组件优化

vue 复制代码
<!-- src/components/ImageGrid.vue -->
<template>
  <view class="grid grid-cols-5 gap-4">
    <!-- 已选图片 -->
    <view
      v-for="(image, index) in images"
      :key="image.id || index"
      class="relative bg-gray-100 rounded-2 overflow-hidden aspect-[53/66]"
    >
      <image
        :src="image.url || image.tempFilePath"
        mode="aspectFill"
        class="w-full h-full"
        @load="onImageLoad"
        @error="onImageError"
      />
      <!-- 上传中遮罩 -->
      <view
        v-if="image.uploading"
        class="absolute inset-0 bg-black/30 flex items-center justify-center"
      >
        <u-loading mode="circle" color="#ffffff" :size="28" />
      </view>
    </view>
  </view>
</template>

<script setup lang="ts">
export interface ImageItem {
  id: string;
  url: string;
  tempFilePath?: string;
  uploading?: boolean;
}

interface Props {
  images: ImageItem[];
  maxCount?: number;
}

const props = withDefaults(defineProps<Props>(), {
  maxCount: 5,
});

const emit = defineEmits<{
  (e: 'imageLoad', event: any): void;
  (e: 'imageError', event: any): void;
}>();

// 图片加载成功
const onImageLoad = (event: any) => {
  emit('imageLoad', event);
};

// 图片加载失败
const onImageError = (event: any) => {
  console.error('图片加载失败:', event);
  emit('imageError', event);
};
</script>

4.4 我追问懒加载实现

txt 复制代码
我:image 组件怎么实现懒加载?
txt 复制代码
AI:微信小程序的 image 组件原生支持懒加载:
vue 复制代码
<template>
  <scroll-view scroll-y class="h-full">
    <view v-for="item in list" :key="item.id" class="mb-4">
      <!-- lazy-load 属性实现懒加载 -->
      <image
        :src="item.imageUrl"
        mode="aspectFill"
        lazy-load
        class="w-full h-40"
        @load="handleImageLoad(item.id)"
        @error="handleImageError(item.id)"
      />
    </view>
  </scroll-view>
</template>

<script setup lang="ts">
// 跟踪图片加载状态
const loadedImages = ref<Set<string>>(new Set());
const failedImages = ref<Set<string>>(new Set());

const handleImageLoad = (id: string) => {
  loadedImages.value.add(id);
};

const handleImageError = (id: string) => {
  failedImages.value.add(id);
};

// 获取图片显示地址(失败时显示默认图)
const getImageSrc = (item: any) => {
  if (failedImages.value.has(item.id)) {
    return '/static/images/default.png';  // 默认图
  }
  return item.imageUrl;
};
</script>
txt 复制代码
AI:lazy-load 的工作原理:

📊 懒加载行为:
1. 图片进入可视区域前不加载
2. 距离可视区域 150px 时开始加载
3. 加载完成触发 load 事件

📌 注意事项:
1. lazy-load 仅在 scroll-view 中生效
2. 首屏关键图片不要用懒加载
3. 建议配合占位图使用

五、渲染优化:减少不必要的计算

5.1 合理使用 computed

txt 复制代码
我:页面数据经常需要格式化,怎么优化?
txt 复制代码
AI:用 computed 缓存计算结果,避免重复计算:
typescript 复制代码
// ❌ 错误:每次渲染都重新计算
const formatPrice = (price: number) => {
  return (price / 100).toFixed(2);
};
// 模板中:{{ formatPrice(item.price) }}

// ✅ 正确:computed 缓存结果
const formattedList = computed(() =>
  list.value.map(item => ({
    ...item,
    formattedPrice: (item.price / 100).toFixed(2),
    formattedDate: formatDate(item.createTime),
  }))
);
// 模板中:{{ item.formattedPrice }}

5.2 v-show vs v-if 的选择

txt 复制代码
AI:根据切换频率选择:
vue 复制代码
<!-- 频繁切换:用 v-show(只是 CSS 隐藏) -->
<view v-show="isExpanded" class="detail-panel">
  {{ detailContent }}
</view>

<!-- 条件渲染:用 v-if(销毁/重建 DOM) -->
<LoginModal v-if="showLogin" @close="showLogin = false" />

<!-- 多分支条件:用 v-if + v-else-if -->
<LoadingState v-if="loading" />
<EmptyState v-else-if="list.length === 0" />
<ContentList v-else :list="list" />
txt 复制代码
AI:选择原则:

📊 使用 v-show 的场景:
- Tab 切换
- 折叠面板
- 下拉菜单
→ 切换频繁,保留 DOM 状态

📊 使用 v-if 的场景:
- 权限控制
- 弹窗组件
- 条件渲染
→ 切换少,节省初始渲染成本

5.3 避免响应式陷阱

txt 复制代码
我:有些静态配置数据,需要 ref 包装吗?
txt 复制代码
AI:静态数据不需要响应式:
typescript 复制代码
// ❌ 不必要的响应式
const menuItems = ref([
  { icon: 'home', text: '首页', path: '/pages/index' },
  { icon: 'user', text: '我的', path: '/pages/my' },
]);

// ✅ 静态数据直接定义
const menuItems = [
  { icon: 'home', text: '首页', path: '/pages/index' },
  { icon: 'user', text: '我的', path: '/pages/my' },
];

// ✅ 需要响应式的数据才用 ref
const selectedIndex = ref(0);
const userInfo = ref<UserInfo | null>(null);
txt 复制代码
AI:响应式的开销:

📊 ref/reactive 的成本:
1. 创建 Proxy 代理对象
2. 依赖收集(track)
3. 触发更新(trigger)

📌 优化建议:
- 静态配置:普通对象/数组
- 列表数据:ref([])
- 表单状态:ref() 或 reactive()
- 大对象频繁更新:shallowRef()

5.4 列表渲染优化

typescript 复制代码
// ❌ 错误:使用 index 作为 key
<view v-for="(item, index) in list" :key="index">

// ✅ 正确:使用唯一 ID 作为 key
<view v-for="item in list" :key="item.id">

// ✅ 复合 key(当 id 不唯一时)
<view v-for="(item, index) in list" :key="`${item.type}-${item.id}`">

六、网络优化:请求合并与缓存

6.1 请求合并

txt 复制代码
我:首页有 5 个接口同时请求,能不能优化?
txt 复制代码
AI:有几种优化策略:
typescript 复制代码
// 策略1:接口合并(需要后端配合)
// 一个接口返回多种数据
const fetchHomeData = async () => {
  const res = await httpGet<{
    userInfo: UserInfo;
    vipInfo: VipInfo;
    replyQuota: QuotaInfo;
  }>('/api/home/init');

  return res.data;
};

// 策略2:并行请求(前端优化)
const fetchHomeDataParallel = async () => {
  const [userRes, vipRes, quotaRes] = await Promise.all([
    getUserInfo(),
    getVipInfo(),
    getReplyQuota(),
  ]);

  return {
    userInfo: userRes.data,
    vipInfo: vipRes.data,
    quota: quotaRes.data,
  };
};

// 策略3:按优先级加载
const fetchHomeDataPriority = async () => {
  // 首屏必须数据:立即加载
  const essential = await httpGet('/api/home/essential');
  renderEssential(essential.data);

  // 次要数据:延迟加载
  setTimeout(async () => {
    const extra = await httpGet('/api/home/extra');
    renderExtra(extra.data);
  }, 100);
};

6.2 数据缓存

typescript 复制代码
// 本地缓存工具
const cache = {
  set(key: string, data: any, ttl: number = 5 * 60 * 1000) {
    const item = {
      data,
      expireAt: Date.now() + ttl,
    };
    uni.setStorageSync(key, JSON.stringify(item));
  },

  get<T>(key: string): T | null {
    const raw = uni.getStorageSync(key);
    if (!raw) return null;

    try {
      const item = JSON.parse(raw);
      if (Date.now() > item.expireAt) {
        uni.removeStorageSync(key);
        return null;
      }
      return item.data as T;
    } catch {
      return null;
    }
  },
};

// 带缓存的请求
const getUserInfoCached = async () => {
  const cacheKey = 'user_info';

  // 先读缓存
  const cached = cache.get<UserInfo>(cacheKey);
  if (cached) return cached;

  // 缓存未命中,请求接口
  const res = await getUserInfo();
  cache.set(cacheKey, res.data, 10 * 60 * 1000); // 缓存 10 分钟

  return res.data;
};

七、Loading 状态:提升感知性能

7.1 加载指示器组件

vue 复制代码
<!-- src/components/LoadingIndicator.vue -->
<template>
  <view class="loading-wrapper">
    <view class="dot-group">
      <view v-for="dot in 3" :key="dot" class="loading-dot" />
    </view>
    <text v-if="text" class="loading-text">{{ text }}</text>
  </view>
</template>

<script setup lang="ts">
defineProps<{
  text?: string;
}>();
</script>

<style scoped>
.loading-wrapper {
  display: flex;
  align-items: center;
  gap: 16rpx;
  color: #706df7;
}

.dot-group {
  display: flex;
  gap: 6rpx;
}

.loading-dot {
  width: 12rpx;
  height: 12rpx;
  border-radius: 9999px;
  background-color: #706df7;
  animation: dotPulse 1.2s ease-in-out infinite;
}

.loading-dot:nth-child(2) { animation-delay: 0.2s; }
.loading-dot:nth-child(3) { animation-delay: 0.4s; }

@keyframes dotPulse {
  0%, 80%, 100% {
    transform: scale(0.6);
    opacity: 0.2;
  }
  40% {
    transform: scale(1);
    opacity: 1;
  }
}
</style>

7.2 骨架屏方案

txt 复制代码
我:首屏加载时能不能显示骨架屏?
txt 复制代码
AI:骨架屏可以提升用户感知速度,
让用户知道"内容正在加载"而不是"页面卡住了":
vue 复制代码
<template>
  <!-- 骨架屏 -->
  <view v-if="loading" class="skeleton">
    <view class="skeleton-header">
      <view class="skeleton-avatar animate-pulse" />
      <view class="skeleton-info">
        <view class="skeleton-line w-24 animate-pulse" />
        <view class="skeleton-line w-16 animate-pulse" />
      </view>
    </view>
    <view class="skeleton-content">
      <view v-for="i in 3" :key="i" class="skeleton-card animate-pulse" />
    </view>
  </view>

  <!-- 实际内容 -->
  <view v-else class="content">
    <UserHeader :user="userInfo" />
    <ReplyList :list="replies" />
  </view>
</template>

<style scoped lang="scss">
.skeleton-avatar {
  @apply w-16 h-16 rounded-full bg-gray-200;
}

.skeleton-line {
  @apply h-4 rounded bg-gray-200 mb-2;
}

.skeleton-card {
  @apply h-24 rounded-xl bg-gray-200 mb-4;
}

.animate-pulse {
  animation: pulse 1.5s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}
</style>

八、性能优化最佳实践

8.1 优化清单

优化项 措施 效果
分包 非核心页面放分包 主包体积 -30%
预加载 配置 preloadRule 分包加载 -50%
构建 bundle-optimizer 重复代码 -20%
图片 懒加载 + CDN 首屏加载 -40%
渲染 computed + v-show 渲染时间 -30%
网络 请求合并 + 缓存 请求数 -60%

8.2 性能监控

typescript 复制代码
// 页面加载耗时监控
const trackPageLoad = () => {
  const startTime = Date.now();

  onReady(() => {
    const loadTime = Date.now() - startTime;

    // 上报性能数据
    uni.reportAnalytics('page_load', {
      page: getCurrentPages().pop()?.route,
      loadTime,
    });

    // 超过阈值告警
    if (loadTime > 2000) {
      console.warn(`页面加载过慢: ${loadTime}ms`);
    }
  });
};

8.3 持续优化

txt 复制代码
AI:性能优化不是一次性工作,需要持续关注:

📊 优化周期:
1. 开发期:遵循性能规范
2. 提审前:体验评分检查
3. 上线后:监控关键指标
4. 定期:分析慢请求和慢渲染

📌 核心指标:
- 首屏加载时间 < 1.5s
- 页面切换时间 < 300ms
- 体验评分 > 90 分
- 内存占用 < 100MB

九、总结:性能优化方法论

9.1 优化顺序

txt 复制代码
📊 优先级排序:

1. 分包优化(收益最大,成本最低)
2. 图片优化(效果明显,易于实施)
3. 构建优化(一次配置,长期受益)
4. 渲染优化(需要理解原理)
5. 网络优化(可能需要后端配合)

9.2 本文优化清单

优化项 类型 核心技术 效果
分包配置 构建 subPackages + preloadRule 主包体积 -30%
Bundle Optimizer 构建 异步导入、依赖优化 代码体积 -20%
图片懒加载 渲染 lazy-load + CDN 首屏加载 -40%
computed 缓存 渲染 避免重复计算 渲染时间 -30%
请求缓存 网络 本地存储 + TTL 请求数 -60%
骨架屏 体验 感知优化 用户体验 +50%

9.3 下一篇预告

《【AI 编程实战】第 12 篇:项目总结与 AI 协作心得》

最后一篇是整个系列的总结:

  • 项目从 0 到 1 的完整回顾
  • AI 辅助开发的效率提升
  • 与 AI 协作的最佳实践

性能优化不是"锦上添花",而是用户体验的基础保障

通过和 AI 对话,系统性地分析问题、制定方案、逐一优化。

如果这篇文章对你有帮助,请点赞、收藏、转发!

相关推荐
mCell4 小时前
如何零成本搭建个人站点
前端·程序员·github
mCell4 小时前
为什么 Memo Code 先做 CLI:以及终端输入框到底有多难搞
前端·设计模式·agent
恋猫de小郭5 小时前
AI 在提高你工作效率的同时,也一直在增加你的疲惫和焦虑
前端·人工智能·ai编程
少云清5 小时前
【安全测试】2_客户端脚本安全测试 _XSS和CSRF
前端·xss·csrf
银烛木5 小时前
黑马程序员前端h5+css3
前端·css·css3
m0_607076605 小时前
CSS3 转换,快手前端面试经验,隔壁都馋哭了
前端·面试·css3
听海边涛声5 小时前
CSS3 图片模糊处理
前端·css·css3
IT、木易5 小时前
css3 backdrop-filter 在移动端 Safari 上导致渲染性能急剧下降的优化方案有哪些?
前端·css3·safari
0思必得05 小时前
[Web自动化] Selenium无头模式
前端·爬虫·selenium·自动化·web自动化
anOnion5 小时前
构建无障碍组件之Dialog Pattern
前端·html·交互设计