uni-app 本地存储

核心知识点

本地存储 API

uni-app 提供了多种本地存储 API,包括同步和异步方法,用于在不同平台上实现数据的本地存储。

1. 同步方法

// 存储数据
uni.setStorageSync('key', 'value');

// 获取数据
const value = uni.getStorageSync('key');

// 删除数据
uni.removeStorageSync('key');

// 清空所有数据
uni.clearStorageSync();

// 获取所有键
const keys = uni.getStorageInfoSync().keys;

2. 异步方法

// 存储数据
uni.setStorage({
  key: 'key',
  data: 'value',
  success: () => {
    console.log('存储成功');
  },
  fail: (err) => {
    console.log('存储失败:', err);
  }
});

// 获取数据
uni.getStorage({
  key: 'key',
  success: (res) => {
    console.log('获取成功:', res.data);
  },
  fail: (err) => {
    console.log('获取失败:', err);
  }
});

// 删除数据
uni.removeStorage({
  key: 'key',
  success: () => {
    console.log('删除成功');
  },
  fail: (err) => {
    console.log('删除失败:', err);
  }
});

// 清空所有数据
uni.clearStorage({
  success: () => {
    console.log('清空成功');
  },
  fail: (err) => {
    console.log('清空失败:', err);
  }
});

// 获取存储信息
uni.getStorageInfo({
  success: (res) => {
    console.log('存储键列表:', res.keys);
    console.log('当前存储大小:', res.currentSize);
    console.log('存储限制大小:', res.limitSize);
  }
});

数据持久化

确保数据在应用重启后仍然存在,实现数据的持久化存储。

1. 基本数据类型

对于字符串、数字、布尔值等基本数据类型,可以直接存储:

// 存储字符串
uni.setStorageSync('username', 'admin');

// 存储数字
uni.setStorageSync('age', 25);

// 存储布尔值
uni.setStorageSync('isLogin', true);

2. 复杂数据类型

对于对象、数组等复杂数据类型,需要先转换为 JSON 字符串:

// 存储对象
const user = {
  id: 1,
  username: 'admin',
  email: 'admin@example.com'
};
uni.setStorageSync('user', JSON.stringify(user));

// 获取对象
const storedUser = JSON.parse(uni.getStorageSync('user'));
console.log(storedUser.username); // 输出: admin

// 存储数组
const list = [1, 2, 3, 4, 5];
uni.setStorageSync('list', JSON.stringify(list));

// 获取数组
const storedList = JSON.parse(uni.getStorageSync('list'));
console.log(storedList.length); // 输出: 5

缓存策略

设计合理的缓存策略,提高应用性能和用户体验。

1. 缓存过期时间

为缓存数据设置过期时间,定期清理过期数据:

// 存储带过期时间的数据
function setCacheWithExpire(key, data, expire = 3600000) {
  const cacheData = {
    data,
    expire: Date.now() + expire
  };
  uni.setStorageSync(key, JSON.stringify(cacheData));
}

// 获取缓存数据
function getCacheWithExpire(key) {
  const cached = uni.getStorageSync(key);
  if (!cached) return null;
  
  const cacheData = JSON.parse(cached);
  if (Date.now() > cacheData.expire) {
    // 缓存过期,删除数据
    uni.removeStorageSync(key);
    return null;
  }
  
  return cacheData.data;
}

// 使用示例
setCacheWithExpire('userList', [{ id: 1, name: '张三' }], 5 * 60 * 1000); // 5分钟过期
const userList = getCacheWithExpire('userList');

2. 缓存大小管理

监控和管理缓存大小,避免超出平台限制:

// 检查缓存大小
function checkStorageSize() {
  const info = uni.getStorageInfoSync();
  console.log(`当前存储大小: ${info.currentSize}KB`);
  console.log(`存储限制大小: ${info.limitSize}KB`);
  
  // 当存储大小超过限制的 80% 时,清理过期数据
  if (info.currentSize > info.limitSize * 0.8) {
    clearExpiredCache();
  }
}

// 清理过期数据
function clearExpiredCache() {
  const info = uni.getStorageInfoSync();
  info.keys.forEach(key => {
    const cached = uni.getStorageSync(key);
    try {
      const cacheData = JSON.parse(cached);
      if (cacheData.expire && Date.now() > cacheData.expire) {
        uni.removeStorageSync(key);
        console.log(`清理过期缓存: ${key}`);
      }
    } catch (e) {
      // 非 JSON 格式数据,跳过
    }
  });
}

// 定期检查缓存大小
setInterval(checkStorageSize, 5 * 60 * 1000); // 每5分钟检查一次

3. 缓存键命名规范

使用统一的缓存键命名规范,方便管理和维护:

// 缓存键命名规范
const CACHE_KEYS = {
  USER_INFO: 'user_info',
  TOKEN: 'token',
  USER_SETTINGS: 'user_settings',
  SEARCH_HISTORY: 'search_history',
  CACHE_PREFIX: 'cache_',
  
  // 生成带前缀的缓存键
  generateKey: function(key) {
    return this.CACHE_PREFIX + key;
  }
};

// 使用示例
uni.setStorageSync(CACHE_KEYS.USER_INFO, JSON.stringify(userInfo));
const userInfo = JSON.parse(uni.getStorageSync(CACHE_KEYS.USER_INFO));

// 生成带前缀的缓存键
const cacheKey = CACHE_KEYS.generateKey('api_data_' + Date.now());
uni.setStorageSync(cacheKey, JSON.stringify(apiData));

实用案例

实现用户登录状态管理

1. 登录状态存储

// utils/auth.js

// 存储登录状态
export function saveLoginState(userInfo, token) {
  try {
    uni.setStorageSync('userInfo', JSON.stringify(userInfo));
    uni.setStorageSync('token', token);
    uni.setStorageSync('isLogin', true);
    return true;
  } catch (error) {
    console.error('存储登录状态失败:', error);
    return false;
  }
}

// 获取登录状态
export function getLoginState() {
  try {
    const isLogin = uni.getStorageSync('isLogin');
    const userInfo = uni.getStorageSync('userInfo');
    const token = uni.getStorageSync('token');
    
    if (isLogin && userInfo && token) {
      return {
        isLogin: true,
        userInfo: JSON.parse(userInfo),
        token
      };
    }
    return { isLogin: false };
  } catch (error) {
    console.error('获取登录状态失败:', error);
    return { isLogin: false };
  }
}

// 清除登录状态
export function clearLoginState() {
  try {
    uni.removeStorageSync('userInfo');
    uni.removeStorageSync('token');
    uni.removeStorageSync('isLogin');
    return true;
  } catch (error) {
    console.error('清除登录状态失败:', error);
    return false;
  }
}

// 检查登录状态
export function checkLogin() {
  const loginState = getLoginState();
  if (!loginState.isLogin) {
    uni.showToast({
      title: '请先登录',
      icon: 'none'
    });
    uni.navigateTo({
      url: '/pages/login/login'
    });
    return false;
  }
  return true;
}

2. 登录页面

<template>
  <view class="login-container">
    <view class="form-item">
      <text>用户名</text>
      <input v-model="username" placeholder="请输入用户名" />
    </view>
    <view class="form-item">
      <text>密码</text>
      <input v-model="password" type="password" placeholder="请输入密码" />
    </view>
    <button class="login-button" @click="login">登录</button>
    <button class="logout-button" @click="logout" v-if="isLogin">退出登录</button>
  </view>
</template>

<script>
import { saveLoginState, clearLoginState, getLoginState } from '../../utils/auth';
import request from '../../utils/request';

export default {
  data() {
    return {
      username: '',
      password: '',
      isLogin: false
    };
  },
  onLoad() {
    // 检查登录状态
    const loginState = getLoginState();
    this.isLogin = loginState.isLogin;
  },
  methods: {
    async login() {
      if (!this.username || !this.password) {
        uni.showToast({
          title: '请输入用户名和密码',
          icon: 'none'
        });
        return;
      }

      try {
        const res = await request.post('/login', {
          username: this.username,
          password: this.password
        });

        // 保存登录状态
        const success = saveLoginState(res.user, res.token);
        if (success) {
          this.isLogin = true;
          uni.showToast({
            title: '登录成功',
            icon: 'success'
          });
          uni.switchTab({
            url: '/pages/index/index'
          });
        } else {
          uni.showToast({
            title: '登录失败,请重试',
            icon: 'none'
          });
        }
      } catch (error) {
        console.error('登录失败:', error);
        uni.showToast({
          title: '登录失败,请检查账号密码',
          icon: 'none'
        });
      }
    },
    logout() {
      const success = clearLoginState();
      if (success) {
        this.isLogin = false;
        uni.showToast({
          title: '退出登录成功',
          icon: 'success'
        });
      } else {
        uni.showToast({
          title: '退出登录失败',
          icon: 'none'
        });
      }
    }
  }
};
</script>

<style scoped>
.login-container {
  padding: 40rpx;
}

.form-item {
  margin-bottom: 30rpx;
}

.form-item text {
  display: block;
  margin-bottom: 10rpx;
  font-size: 28rpx;
  color: #333;
}

.form-item input {
  width: 100%;
  height: 80rpx;
  border: 1rpx solid #e5e5e5;
  border-radius: 8rpx;
  padding: 0 20rpx;
  font-size: 28rpx;
}

.login-button {
  width: 100%;
  height: 80rpx;
  background-color: #007aff;
  color: #fff;
  border-radius: 8rpx;
  font-size: 32rpx;
  margin-top: 40rpx;
}

.logout-button {
  width: 100%;
  height: 80rpx;
  background-color: #ff3b30;
  color: #fff;
  border-radius: 8rpx;
  font-size: 32rpx;
  margin-top: 20rpx;
}
</style>

3. 全局登录状态检查

App.vue 中检查登录状态:

<script>
import { getLoginState } from './utils/auth';

export default {
  onLaunch() {
    console.log('App Launch');
    // 检查登录状态
    const loginState = getLoginState();
    if (loginState.isLogin) {
      console.log('用户已登录:', loginState.userInfo.username);
      // 可以在这里执行登录后的初始化操作
    } else {
      console.log('用户未登录');
    }
  },
  onShow() {
    console.log('App Show');
  },
  onHide() {
    console.log('App Hide');
  }
};
</script>

<style>
/* 全局样式 */
page {
  background-color: #f5f5f5;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}
</style>

实现搜索历史管理

<template>
  <view class="search-container">
    <view class="search-bar">
      <input v-model="keyword" placeholder="请输入搜索关键词" />
      <button class="search-button" @click="search">搜索</button>
    </view>
    
    <view class="history-section" v-if="searchHistory.length > 0">
      <view class="history-header">
        <text>搜索历史</text>
        <button class="clear-button" @click="clearHistory">清空</button>
      </view>
      <view class="history-tags">
        <view class="tag" v-for="(item, index) in searchHistory" :key="index" @click="selectHistory(item)">
          {{ item }}
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      keyword: '',
      searchHistory: []
    };
  },
  onLoad() {
    // 获取搜索历史
    this.getSearchHistory();
  },
  methods: {
    search() {
      if (!this.keyword.trim()) return;
      
      // 执行搜索操作
      console.log('搜索:', this.keyword);
      
      // 保存搜索历史
      this.saveSearchHistory(this.keyword.trim());
      
      // 跳转到搜索结果页面
      uni.navigateTo({
        url: `/pages/search-result/search-result?keyword=${encodeURIComponent(this.keyword)}`
      });
    },
    
    // 获取搜索历史
    getSearchHistory() {
      const history = uni.getStorageSync('searchHistory');
      if (history) {
        this.searchHistory = JSON.parse(history);
      }
    },
    
    // 保存搜索历史
    saveSearchHistory(keyword) {
      // 去重,将新关键词放在最前面
      let history = uni.getStorageSync('searchHistory');
      if (history) {
        history = JSON.parse(history);
        // 移除重复项
        history = history.filter(item => item !== keyword);
      } else {
        history = [];
      }
      
      // 添加到开头
      history.unshift(keyword);
      
      // 限制历史记录数量
      if (history.length > 10) {
        history = history.slice(0, 10);
      }
      
      // 保存到本地存储
      uni.setStorageSync('searchHistory', JSON.stringify(history));
      this.searchHistory = history;
    },
    
    // 清空搜索历史
    clearHistory() {
      uni.removeStorageSync('searchHistory');
      this.searchHistory = [];
    },
    
    // 选择历史记录
    selectHistory(item) {
      this.keyword = item;
      this.search();
    }
  }
};
</script>

<style scoped>
.search-container {
  padding: 20rpx;
}

.search-bar {
  display: flex;
  margin-bottom: 30rpx;
}

.search-bar input {
  flex: 1;
  height: 80rpx;
  border: 1rpx solid #e5e5e5;
  border-radius: 8rpx 0 0 8rpx;
  padding: 0 20rpx;
  font-size: 28rpx;
  background-color: #fff;
}

.search-button {
  width: 120rpx;
  height: 80rpx;
  background-color: #007aff;
  color: #fff;
  border-radius: 0 8rpx 8rpx 0;
  font-size: 28rpx;
}

.history-section {
  margin-top: 20rpx;
}

.history-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20rpx;
}

.history-header text {
  font-size: 28rpx;
  font-weight: bold;
  color: #333;
}

.clear-button {
  font-size: 24rpx;
  color: #999;
  background-color: transparent;
}

.history-tags {
  display: flex;
  flex-wrap: wrap;
}

.tag {
  padding: 10rpx 20rpx;
  background-color: #f0f0f0;
  border-radius: 16rpx;
  font-size: 24rpx;
  margin-right: 10rpx;
  margin-bottom: 10rpx;
}
</style>

学习目标

通过本集的学习,你应该能够:

  1. 掌握 uni-app 本地存储 API 的使用方法,包括同步和异步方法
  2. 学会存储和获取不同类型的数据,包括基本数据类型和复杂数据类型
  3. 熟悉数据持久化的实现方法,确保数据在应用重启后仍然存在
  4. 掌握缓存策略的设计方法,包括缓存过期时间、缓存大小管理和缓存键命名规范
  5. 能够通过实用案例实现用户登录状态管理和搜索历史管理等功能

小结

本地存储是 uni-app 开发中的重要部分,合理的本地存储策略可以提高应用的性能和用户体验。通过本集的学习,你已经掌握了本地存储 API、数据持久化、缓存策略等核心知识点,并通过实际案例了解了如何实现用户登录状态管理和搜索历史管理。在后续的开发中,你可以根据实际需求,灵活运用这些知识,构建更加功能强大的应用。

« 上一篇 uni-app 数据请求 下一篇 » uni-app 媒体功能