uni-app 网络优化

核心知识点

1. 网络请求优化

网络请求是应用与服务器交互的重要环节,优化网络请求可以显著提升应用性能和用户体验。

1.1 请求合并与批量处理

  • 合并多个请求:将多个小请求合并为一个大请求,减少网络连接次数
  • 批量处理数据:对于需要多次请求的数据,采用批量获取的方式
  • 使用 GraphQL:如果后端支持,使用 GraphQL 减少冗余数据传输

1.2 请求时机优化

  • 预加载:在用户可能需要数据之前提前加载
  • 懒加载:仅在需要时才加载数据
  • 合理设置请求时机:避免在页面初始化时同时发送过多请求

1.3 请求参数优化

  • 减少请求参数大小:只传递必要的参数
  • 使用 POST 而非 GET:对于大数据量的请求,使用 POST 方法
  • 压缩请求数据:对较大的请求数据进行压缩

2. 数据压缩与传输优化

2.1 响应数据压缩

  • 启用 GZIP 压缩:确保服务器启用 GZIP 压缩
  • 选择合适的数据格式:JSON 通常比 XML 更紧凑
  • 使用 Protocol Buffers:对于性能要求高的场景,考虑使用 Protocol Buffers

2.2 数据传输优化

  • 使用 HTTPS:虽然 HTTPS 有一定的性能开销,但提供了安全保障
  • 启用 HTTP/2:利用 HTTP/2 的多路复用特性
  • 避免重定向:减少 301/302 重定向
  • 使用 CDN:对于静态资源,使用 CDN 加速

3. 缓存策略

3.1 HTTP 缓存

  • 合理设置 Cache-Control 头:利用浏览器缓存
  • 使用 ETag 和 Last-Modified:实现高效的缓存验证
  • 设置适当的缓存过期时间:根据数据更新频率设置

3.2 本地缓存

  • 使用 uni.setStorageSync/uni.setStorage:缓存不常变化的数据
  • 实现多级缓存:内存缓存 + 本地存储缓存
  • 缓存策略选择:根据数据类型选择合适的缓存策略

3.3 离线缓存

  • 实现 Service Worker:在 Web 端启用离线缓存
  • 预缓存关键资源:确保核心功能在离线时也能使用
  • 数据同步策略:实现离线数据的同步机制

4. 网络错误处理与重试机制

  • 合理的错误处理:对网络错误进行友好的提示
  • 智能重试策略:对临时性错误进行有限次数的重试
  • 降级方案:当网络不可用时,提供合理的降级方案
  • 网络状态监测:监测网络状态变化,及时调整应用行为

实用案例分析

案例一:实现请求拦截器和响应拦截器

问题描述

在应用中,每个网络请求都需要添加认证信息、处理错误等重复逻辑,导致代码冗余。

解决方案

使用请求拦截器和响应拦截器统一处理请求前和响应后的逻辑。

代码示例

// api/request.js
class Request {
  constructor() {
    this.baseURL = 'https://api.example.com';
    this.timeout = 10000;
    this.retryCount = 3;
  }

  // 请求拦截器
  requestInterceptor(config) {
    // 添加认证token
    const token = uni.getStorageSync('token');
    if (token) {
      config.header = {
        ...config.header,
        'Authorization': `Bearer ${token}`
      };
    }
    // 添加时间戳,避免缓存
    if (config.method === 'GET') {
      config.url = `${config.url}${config.url.includes('?') ? '&' : '?'}t=${Date.now()}`;
    }
    return config;
  }

  // 响应拦截器
  responseInterceptor(response) {
    if (response.statusCode === 200) {
      return response.data;
    } else {
      this.handleError(response);
      return Promise.reject(response);
    }
  }

  // 错误处理
  handleError(response) {
    switch (response.statusCode) {
      case 401:
        uni.showToast({ title: '未授权,请重新登录', icon: 'none' });
        // 跳转到登录页
        uni.navigateTo({ url: '/pages/login/login' });
        break;
      case 403:
        uni.showToast({ title: '拒绝访问', icon: 'none' });
        break;
      case 404:
        uni.showToast({ title: '请求地址不存在', icon: 'none' });
        break;
      case 500:
        uni.showToast({ title: '服务器内部错误', icon: 'none' });
        break;
      default:
        uni.showToast({ title: `请求失败:${response.statusCode}`, icon: 'none' });
    }
  }

  // 发送请求
  async request(options) {
    const config = this.requestInterceptor({
      baseURL: this.baseURL,
      timeout: this.timeout,
      ...options
    });

    let retry = 0;
    while (retry < this.retryCount) {
      try {
        const response = await uni.request(config);
        return this.responseInterceptor(response[1]);
      } catch (error) {
        retry++;
        if (retry >= this.retryCount) {
          uni.showToast({ title: '网络请求失败,请检查网络连接', icon: 'none' });
          return Promise.reject(error);
        }
        // 延迟重试
        await new Promise(resolve => setTimeout(resolve, 1000 * retry));
      }
    }
  }

  // 快捷方法
  get(url, data, config = {}) {
    return this.request({
      method: 'GET',
      url,
      data,
      ...config
    });
  }

  post(url, data, config = {}) {
    return this.request({
      method: 'POST',
      url,
      data,
      ...config
    });
  }

  put(url, data, config = {}) {
    return this.request({
      method: 'PUT',
      url,
      data,
      ...config
    });
  }

  delete(url, data, config = {}) {
    return this.request({
      method: 'DELETE',
      url,
      data,
      ...config
    });
  }
}

export default new Request();

案例二:实现多级缓存策略

问题描述

应用中频繁请求相同的数据,导致网络请求过多,影响性能。

解决方案

实现多级缓存策略,包括内存缓存和本地存储缓存,减少重复的网络请求。

代码示例

// utils/cache.js
class CacheManager {
  constructor() {
    this.memoryCache = new Map();
    this.memoryCacheExpire = new Map();
  }

  // 设置缓存
  set(key, value, expire = 3600000) { // 默认缓存1小时
    // 设置内存缓存
    this.memoryCache.set(key, value);
    this.memoryCacheExpire.set(key, Date.now() + expire);
    
    // 设置本地存储缓存
    try {
      uni.setStorageSync(key, {
        value,
        expire: Date.now() + expire
      });
    } catch (e) {
      console.error('设置本地缓存失败:', e);
    }
  }

  // 获取缓存
  get(key) {
    // 先从内存缓存获取
    if (this.memoryCache.has(key)) {
      const expire = this.memoryCacheExpire.get(key);
      if (Date.now() < expire) {
        return this.memoryCache.get(key);
      } else {
        // 内存缓存过期,清除
        this.memoryCache.delete(key);
        this.memoryCacheExpire.delete(key);
      }
    }

    // 从本地存储获取
    try {
      const cache = uni.getStorageSync(key);
      if (cache && Date.now() < cache.expire) {
        // 更新内存缓存
        this.memoryCache.set(key, cache.value);
        this.memoryCacheExpire.set(key, cache.expire);
        return cache.value;
      } else {
        // 本地缓存过期,清除
        uni.removeStorageSync(key);
      }
    } catch (e) {
      console.error('获取本地缓存失败:', e);
    }

    return null;
  }

  // 删除缓存
  remove(key) {
    this.memoryCache.delete(key);
    this.memoryCacheExpire.delete(key);
    try {
      uni.removeStorageSync(key);
    } catch (e) {
      console.error('删除本地缓存失败:', e);
    }
  }

  // 清除所有缓存
  clear() {
    this.memoryCache.clear();
    this.memoryCacheExpire.clear();
    try {
      uni.clearStorageSync();
    } catch (e) {
      console.error('清除本地缓存失败:', e);
    }
  }
}

export default new CacheManager();

使用示例

import request from '@/api/request';
import cache from '@/utils/cache';

// 获取用户信息
export const getUserInfo = async () => {
  const cacheKey = 'userInfo';
  
  // 先尝试从缓存获取
  const cachedData = cache.get(cacheKey);
  if (cachedData) {
    return cachedData;
  }
  
  // 缓存不存在,从服务器获取
  const data = await request.get('/user/info');
  
  // 设置缓存,缓存10分钟
  cache.set(cacheKey, data, 10 * 60 * 1000);
  
  return data;
};

// 获取商品列表
export const getGoodsList = async (params) => {
  const cacheKey = `goodsList_${JSON.stringify(params)}`;
  
  // 先尝试从缓存获取
  const cachedData = cache.get(cacheKey);
  if (cachedData) {
    return cachedData;
  }
  
  // 缓存不存在,从服务器获取
  const data = await request.get('/goods/list', params);
  
  // 设置缓存,缓存5分钟
  cache.set(cacheKey, data, 5 * 60 * 1000);
  
  return data;
};

案例三:实现图片懒加载

问题描述

页面中存在大量图片,一次性加载所有图片会导致页面加载缓慢,消耗过多流量。

解决方案

实现图片懒加载,仅在图片进入视口时才加载。

代码示例

<template>
  <view class="lazy-load-demo">
    <view class="image-item" v-for="(item, index) in images" :key="index">
      <image 
        v-lazy="item.url" 
        :data-src="item.url" 
        class="lazy-image" 
        :style="{ height: item.height + 'px' }"
      />
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      images: [
        { url: 'https://example.com/image1.jpg', height: 200 },
        { url: 'https://example.com/image2.jpg', height: 250 },
        { url: 'https://example.com/image3.jpg', height: 300 },
        { url: 'https://example.com/image4.jpg', height: 200 },
        { url: 'https://example.com/image5.jpg', height: 250 },
        { url: 'https://example.com/image6.jpg', height: 300 },
        { url: 'https://example.com/image7.jpg', height: 200 },
        { url: 'https://example.com/image8.jpg', height: 250 },
        { url: 'https://example.com/image9.jpg', height: 300 },
        { url: 'https://example.com/image10.jpg', height: 200 }
      ]
    };
  },
  directives: {
    lazy: {
      inserted(el, binding) {
        // 设置占位图
        el.src = 'https://example.com/placeholder.jpg';
        
        // 创建观察者
        const observer = new IntersectionObserver((entries) => {
          entries.forEach(entry => {
            if (entry.isIntersecting) {
              // 图片进入视口,加载真实图片
              el.src = binding.value;
              // 停止观察
              observer.unobserve(el);
            }
          });
        }, {
          threshold: 0.1
        });
        
        // 开始观察
        observer.observe(el);
        
        // 保存观察者实例,以便后续清理
        el._observer = observer;
      },
      unbind(el) {
        // 清理观察者
        if (el._observer) {
          el._observer.disconnect();
        }
      }
    }
  }
};
</script>

<style scoped>
.lazy-load-demo {
  padding: 20rpx;
}

.image-item {
  margin-bottom: 20rpx;
  overflow: hidden;
  border-radius: 8rpx;
}

.lazy-image {
  width: 100%;
  transition: opacity 0.3s ease;
}

.lazy-image[src="https://example.com/placeholder.jpg"] {
  opacity: 0.5;
}
</style>

案例四:实现网络状态监测与自动重试

问题描述

应用在网络不稳定的情况下,用户体验较差,需要手动刷新才能重新加载数据。

解决方案

实现网络状态监测,当网络从离线恢复到在线时,自动重试失败的请求。

代码示例

// utils/network.js
class NetworkManager {
  constructor() {
    this.isOnline = true;
    this.pendingRequests = [];
    this.initNetworkMonitor();
  }

  // 初始化网络监测
  initNetworkMonitor() {
    // 监听网络状态变化
    uni.onNetworkStatusChange(res => {
      this.isOnline = res.isConnected;
      
      if (res.isConnected) {
        // 网络恢复,处理待处理的请求
        this.handlePendingRequests();
        uni.showToast({ title: '网络已恢复', icon: 'success' });
      } else {
        uni.showToast({ title: '网络连接已断开', icon: 'none' });
      }
    });

    // 初始检查
    uni.getNetworkType({
      success: (res) => {
        this.isOnline = res.networkType !== 'none';
      }
    });
  }

  // 添加待处理的请求
  addPendingRequest(requestFn) {
    this.pendingRequests.push(requestFn);
  }

  // 处理待处理的请求
  handlePendingRequests() {
    while (this.pendingRequests.length > 0) {
      const requestFn = this.pendingRequests.shift();
      try {
        requestFn();
      } catch (error) {
        console.error('处理待处理请求失败:', error);
      }
    }
  }

  // 检查网络状态
  checkNetwork() {
    return new Promise((resolve) => {
      if (this.isOnline) {
        resolve(true);
      } else {
        uni.getNetworkType({
          success: (res) => {
            this.isOnline = res.networkType !== 'none';
            resolve(this.isOnline);
          },
          fail: () => {
            resolve(false);
          }
        });
      }
    });
  }

  // 带网络检查的请求包装
  async requestWithNetworkCheck(requestFn) {
    const isOnline = await this.checkNetwork();
    
    if (isOnline) {
      try {
        return await requestFn();
      } catch (error) {
        // 请求失败,添加到待处理队列
        this.addPendingRequest(requestFn);
        throw error;
      }
    } else {
      // 网络离线,添加到待处理队列
      this.addPendingRequest(requestFn);
      throw new Error('网络连接已断开,请检查网络设置');
    }
  }
}

export default new NetworkManager();

使用示例

import request from '@/api/request';
import networkManager from '@/utils/network';

// 带网络检查的请求
export const getHomeData = async () => {
  return networkManager.requestWithNetworkCheck(() => {
    return request.get('/home/data');
  });
};

export const submitOrder = async (data) => {
  return networkManager.requestWithNetworkCheck(() => {
    return request.post('/order/submit', data);
  });
};

学习目标

  1. 了解 uni-app 应用中网络优化的重要性
  2. 掌握网络请求优化的各种方法和技巧
  3. 学会实现数据压缩和传输优化
  4. 理解并应用各种缓存策略
  5. 掌握网络错误处理和重试机制
  6. 能够根据实际场景选择合适的网络优化策略

网络优化最佳实践

  1. 分析网络性能瓶颈:使用浏览器开发者工具或 uni-app 开发者工具分析网络请求性能
  2. 实施渐进式优化:从最容易实现的优化开始,逐步深入
  3. 监控网络性能:建立网络性能监控系统,及时发现问题
  4. 根据网络环境调整策略:在不同网络环境下使用不同的优化策略
  5. 用户体验优先:优化网络性能的同时,确保用户体验不受影响
  6. 持续优化:网络优化是一个持续的过程,需要不断调整和改进

总结

网络优化是 uni-app 应用开发中一个重要的环节,通过合理的网络请求优化、数据压缩、缓存策略和错误处理,可以显著提升应用的性能和用户体验。

在实际开发中,开发者应该根据应用的具体情况,选择合适的优化策略。例如,对于数据更新频繁的应用,应该注重实时性;对于数据更新不频繁的应用,应该注重缓存策略。

通过本教程的学习,你应该掌握了 uni-app 网络优化的核心知识点和实用技巧,能够在实际项目中应用这些知识,开发出性能优异、用户体验良好的应用。

记住,优秀的应用不仅功能丰富,还要响应迅速、流畅稳定,而良好的网络优化是实现这一目标的关键因素之一。

« 上一篇 uni-app 内存管理 下一篇 » uni-app 渲染优化