Skip to content
alex's blog
Go back

UniApp 性能优化实战指南

编辑页面

性能优化是 UniApp 开发中的重要环节。一个性能优秀的应用不仅能提供流畅的用户体验,还能节省设备资源。本文将分享 UniApp 性能优化的实用技巧。

1. 渲染优化

使用 v-show 替代 v-if

<template>
  <!-- ❌ 不推荐:频繁切换会销毁和创建组件 -->
  <view v-if="show">内容</view>
  
  <!-- ✅ 推荐:只切换显示隐藏,不销毁组件 -->
  <view v-show="show">内容</view>
</template>

合理使用 key

<template>
  <!-- ✅ 使用唯一且稳定的 key -->
  <view v-for="item in list" :key="item.id">
    {{ item.name }}
  </view>
</template>

避免在模板中使用复杂计算

<template>
  <!-- ❌ 不推荐:每次渲染都计算 -->
  <view>{{ complexCalculation() }}</view>
  
  <!-- ✅ 推荐:使用 computed -->
  <view>{{ computedValue }}</view>
</template>

<script>
export default {
  data() {
    return {
      list: []
    }
  },
  computed: {
    computedValue() {
      // 复杂计算,结果会被缓存
      return this.list.reduce((sum, item) => sum + item.value, 0)
    }
  }
}
</script>

使用 Object.freeze 冻结数据

export default {
  data() {
    return {
      // 冻结大列表数据,避免响应式处理
      largeList: Object.freeze([
        // 大量数据
      ])
    }
  }
}

2. 列表优化

使用虚拟列表

<template>
  <!-- 长列表使用 scroll-view -->
  <scroll-view 
    scroll-y 
    class="scroll-container"
    :scroll-top="scrollTop"
    @scroll="handleScroll"
  >
    <view 
      v-for="item in visibleItems" 
      :key="item.id"
      class="list-item"
    >
      {{ item.name }}
    </view>
  </scroll-view>
</template>

<script>
export default {
  data() {
    return {
      allItems: [],
      visibleItems: [],
      scrollTop: 0,
      itemHeight: 100, // 每个列表项的高度
      containerHeight: 0
    }
  },
  onLoad() {
    this.initList()
    this.calculateVisibleItems()
  },
  methods: {
    initList() {
      // 初始化列表数据
      this.allItems = Array.from({ length: 1000 }, (_, i) => ({
        id: i,
        name: `项目 ${i}`
      }))
    },
    
    calculateVisibleItems() {
      const start = Math.floor(this.scrollTop / this.itemHeight)
      const end = start + Math.ceil(this.containerHeight / this.itemHeight) + 1
      
      this.visibleItems = this.allItems.slice(start, end)
    },
    
    handleScroll(e) {
      this.scrollTop = e.detail.scrollTop
      this.calculateVisibleItems()
    }
  }
}
</script>

分页加载

<template>
  <scroll-view 
    scroll-y 
    @scrolltolower="loadMore"
    :lower-threshold="100"
  >
    <view v-for="item in list" :key="item.id">
      {{ item.name }}
    </view>
    
    <view v-if="loading" class="loading">加载中...</view>
    <view v-if="noMore" class="no-more">没有更多了</view>
  </scroll-view>
</template>

<script>
export default {
  data() {
    return {
      list: [],
      page: 1,
      pageSize: 20,
      loading: false,
      noMore: false
    }
  },
  onLoad() {
    this.loadData()
  },
  methods: {
    async loadData() {
      if (this.loading || this.noMore) return
      
      this.loading = true
      try {
        const res = await this.fetchList({
          page: this.page,
          pageSize: this.pageSize
        })
        
        if (res.length < this.pageSize) {
          this.noMore = true
        }
        
        this.list = [...this.list, ...res]
        this.page++
      } catch (error) {
        console.error(error)
      } finally {
        this.loading = false
      }
    },
    
    loadMore() {
      this.loadData()
    }
  }
}
</script>

3. 图片优化

图片懒加载

<template>
  <image 
    :src="imageSrc" 
    :lazy-load="true"
    mode="aspectFit"
    @load="handleImageLoad"
    @error="handleImageError"
  />
</template>

图片压缩

// 图片压缩工具
const compressImage = (path, quality = 0.8) => {
  // #ifdef APP-PLUS
  return new Promise((resolve, reject) => {
    plus.compressImage({
      src: path,
      quality: quality,
      success: (result) => {
        resolve(result.target)
      },
      fail: reject
    })
  })
  // #endif
  
  // #ifndef APP-PLUS
  return Promise.resolve(path)
  // #endif
}

// 使用
async function uploadImage(path) {
  const compressedPath = await compressImage(path, 0.8)
  // 上传压缩后的图片
}

使用合适的图片格式

<template>
  <!-- 使用 WebP 格式(H5) -->
  <!-- #ifdef H5 -->
  <image src="/static/image.webp" />
  <!-- #endif -->
  
  <!-- 使用 PNG/JPG(小程序/App) -->
  <!-- #ifndef H5 -->
  <image src="/static/image.png" />
  <!-- #endif -->
</template>

4. 网络请求优化

请求合并

// 合并多个请求
const batchRequest = async (requests) => {
  return Promise.all(requests.map(req => 
    uni.request({
      url: req.url,
      method: req.method || 'GET',
      data: req.data
    })
  ))
}

// 使用
const [userInfo, list] = await batchRequest([
  { url: '/api/user/info' },
  { url: '/api/list' }
])

请求缓存

// 请求缓存工具
const cache = new Map()

const cachedRequest = (url, options = {}) => {
  const cacheKey = `${url}_${JSON.stringify(options.data || {})}`
  
  // 检查缓存
  if (cache.has(cacheKey)) {
    const cached = cache.get(cacheKey)
    if (Date.now() - cached.timestamp < 5 * 60 * 1000) { // 5分钟缓存
      return Promise.resolve(cached.data)
    }
  }
  
  // 发起请求
  return uni.request({
    url,
    ...options
  }).then(res => {
    // 缓存结果
    cache.set(cacheKey, {
      data: res.data,
      timestamp: Date.now()
    })
    return res.data
  })
}

请求去重

// 请求去重
const pendingRequests = new Map()

const dedupeRequest = (url, options = {}) => {
  const requestKey = `${url}_${JSON.stringify(options.data || {})}`
  
  // 如果已有相同请求在进行,返回该请求的 Promise
  if (pendingRequests.has(requestKey)) {
    return pendingRequests.get(requestKey)
  }
  
  // 创建新请求
  const requestPromise = uni.request({
    url,
    ...options
  }).finally(() => {
    // 请求完成后移除
    pendingRequests.delete(requestKey)
  })
  
  pendingRequests.set(requestKey, requestPromise)
  return requestPromise
}

5. 代码优化

按需引入

// ❌ 不推荐:全量引入
import * as utils from '@/utils'

// ✅ 推荐:按需引入
import { formatDate, formatPrice } from '@/utils/format'

使用 Tree-shaking

// package.json
{
  "sideEffects": false
}

// 使用 ES6 模块
export function util1() {}
export function util2() {}

// 按需导入
import { util1 } from './utils'

代码分割

// 路由懒加载
const routes = [
  {
    path: '/pages/detail/detail',
    component: () => import('@/pages/detail/detail.vue')
  }
]

6. 内存优化

及时清理定时器

export default {
  data() {
    return {
      timer: null
    }
  },
  onLoad() {
    this.timer = setInterval(() => {
      // 定时任务
    }, 1000)
  },
  onUnload() {
    // 清理定时器
    if (this.timer) {
      clearInterval(this.timer)
      this.timer = null
    }
  }
}

清理事件监听

export default {
  onLoad() {
    // 监听事件
    uni.onPageScroll(this.handleScroll)
  },
  onUnload() {
    // 移除监听
    uni.offPageScroll(this.handleScroll)
  },
  methods: {
    handleScroll() {
      // 滚动处理
    }
  }
}

避免内存泄漏

export default {
  data() {
    return {
      largeData: null
    }
  },
  onUnload() {
    // 清理大对象
    this.largeData = null
  }
}

7. 启动优化

减少启动页资源

// manifest.json
{
  "app-plus": {
    "splashscreen": {
      "autoclose": true,
      "waiting": true
    }
  }
}

延迟加载非关键资源

export default {
  onLoad() {
    // 立即加载关键数据
    this.loadCriticalData()
    
    // 延迟加载非关键数据
    setTimeout(() => {
      this.loadNonCriticalData()
    }, 1000)
  },
  methods: {
    async loadCriticalData() {
      // 关键数据
    },
    async loadNonCriticalData() {
      // 非关键数据
    }
  }
}

8. 动画优化

使用 CSS 动画

<style>
/* ✅ 推荐:使用 CSS 动画 */
.fade-in {
  animation: fadeIn 0.3s ease-in-out;
}

@keyframes fadeIn {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* ❌ 不推荐:使用 JavaScript 动画 */
</style>

使用 transform 替代 position

<style>
/* ✅ 推荐:使用 transform */
.move {
  transform: translateX(100px);
}

/* ❌ 不推荐:使用 position */
.move {
  left: 100px;
}
</style>

9. 打包优化

压缩代码

// manifest.json
{
  "h5": {
    "optimization": {
      "treeShaking": {
        "enable": true
      }
    }
  }
}

移除 console

// 生产环境移除 console
// #ifndef H5
if (process.env.NODE_ENV === 'production') {
  console.log = () => {}
  console.warn = () => {}
  console.error = () => {}
}
// #endif

10. 性能监控

性能指标收集

// 页面加载时间
const pageLoadTime = {
  start: 0,
  end: 0,
  
  start() {
    this.start = Date.now()
  },
  
  end() {
    this.end = Date.now()
    const loadTime = this.end - this.start
    console.log('页面加载时间:', loadTime + 'ms')
    
    // 上报性能数据
    this.reportPerformance(loadTime)
  },
  
  reportPerformance(loadTime) {
    // 上报到服务器
    uni.request({
      url: '/api/performance',
      method: 'POST',
      data: {
        loadTime,
        page: getCurrentPages()[0].route
      }
    })
  }
}

// 使用
export default {
  onLoad() {
    pageLoadTime.start()
  },
  onReady() {
    pageLoadTime.end()
  }
}

性能检查清单

总结

UniApp 性能优化需要从多个方面入手:

  1. 渲染优化:减少不必要的渲染
  2. 列表优化:虚拟列表、分页加载
  3. 资源优化:图片压缩、懒加载
  4. 网络优化:请求合并、缓存、去重
  5. 代码优化:按需引入、代码分割
  6. 内存优化:及时清理资源
  7. 启动优化:减少启动资源
  8. 动画优化:使用 CSS 动画
  9. 打包优化:代码压缩、移除调试代码
  10. 性能监控:收集和分析性能数据

记住:先测量,再优化。使用性能分析工具找出瓶颈,然后有针对性地优化。


性能优化是一个持续的过程,需要在实际开发中不断实践和总结。希望这些技巧能帮助你构建更高效的 UniApp 应用!


编辑页面
Share this post on:

Previous Post
UniApp nvue 与 vue 的区别详解
Next Post
UniApp 跨平台开发最佳实践