Appearance
本地缓存状态(useStorageMap)
一种更好的管理本地缓存状态解决方案
背景介绍
在项目开发中,通常需要保存一些不太重要的状态信息,例如用户是否已查看提示或已阅读某条消息,以便在下次进入应用时不再弹出提醒。通常,我们会将这些信息存储在 localStorage
中。
回想一下,我们会如何编写这种逻辑代码?大致是这样的:
vue
<script setup>
const showMessage = ref(uni.getStorageSync('viewed_message') || true)
const onCloseMessage = () => {
showMessage.value = false
uni.setStorageSync('viewed_message', false)
}
</script>
<template>
...
<view v-if="showMessage" class="message" @click="onCloseMessage">
message...
</view>
</template>
<script setup>
const showMessage = ref(uni.getStorageSync('viewed_message') || true)
const onCloseMessage = () => {
showMessage.value = false
uni.setStorageSync('viewed_message', false)
}
</script>
<template>
...
<view v-if="showMessage" class="message" @click="onCloseMessage">
message...
</view>
</template>
还有另外一种情况,我们需要记录的状态不仅仅是某个单一的状态,而是一系列的状态。举例来说,有一个应用,允许用户在不同的地区之间切换。初次进入某个地区时,需要显示欢迎信息,再次切换回相同地区时,就不再需要显示欢迎。
另一个例子是某个活动中,用户可以进行多次抽奖,有可能多次中奖。当用户有未读的中奖记录时,需要提醒,后面就不再需要提醒,但如果后续还有其他中奖记录,需要再次提醒。
在这种情况下,我们的代码就会变成类似这样:
js
const area = '广东'
const showMessage = ref(uni.getStorageSync(`viewed_welcome_${area}`) || false)
const area = '广东'
const showMessage = ref(uni.getStorageSync(`viewed_welcome_${area}`) || false)
为了更好地管理这些状态,通常会将一系列相关的状态放在同一个列表中。这种方法可以降低 localStorage
中的 key 数量,同时也有助于代码的维护。
js
const viewWelcomeList = uni.getStorageSync(`viewed_welcome_list`) || []
const area = '广东'
const showMessage = ref(viewWelcomeList.includes(area))
const onCloseMessage = () => {
showMessage.value = false
viewWelcomeList.push(area)
// 还需避免数据重复
uni.setStorageSync('viewed_welcome_list', [...new Set(viewWelcomeList)])
}
const viewWelcomeList = uni.getStorageSync(`viewed_welcome_list`) || []
const area = '广东'
const showMessage = ref(viewWelcomeList.includes(area))
const onCloseMessage = () => {
showMessage.value = false
viewWelcomeList.push(area)
// 还需避免数据重复
uni.setStorageSync('viewed_welcome_list', [...new Set(viewWelcomeList)])
}
但这样仍然非常麻烦,你应该能够想象,随着这种代码的增多,读取和更新状态都会变得非常难维护。
因此,useStorageMap
就应运而生,它能够很方便地帮助我们解决此类问题。
基本使用
读取和更新
对于前面那个地区切换的例子,我们只需要这么做:
js
import {useStorageMap} from '@ifanrx/uni-mp'
const area = '广东'
const viewAreaWelcome = useStorageMap('view_area_welcome')
const showMessage = computed(() => !viewAreaWelcome.get(area))
const onCloseMessage = () => {
viewAreaWelcome.set(area)
}
import {useStorageMap} from '@ifanrx/uni-mp'
const area = '广东'
const viewAreaWelcome = useStorageMap('view_area_welcome')
const showMessage = computed(() => !viewAreaWelcome.get(area))
const onCloseMessage = () => {
viewAreaWelcome.set(area)
}
如此一来,当我们调用 viewAreaWelcome.set
的时候,showMessage
就会自动更新。
如果情况再复杂点,需要精确到省市区,可以改成这样:
js
import {useStorageMap} from '@ifanrx/uni-mp'
// useStorageMap 内部会将数组合并成字符串作为每条记录的 key
const area = ['广东', '广州', '海珠']
const viewAreaWelcome = useStorageMap('view_area_welcome')
const showMessage = computed(() => !viewAreaWelcome.get(area))
const onCloseMessage = () => {
viewAreaWelcome.set(area)
}
import {useStorageMap} from '@ifanrx/uni-mp'
// useStorageMap 内部会将数组合并成字符串作为每条记录的 key
const area = ['广东', '广州', '海珠']
const viewAreaWelcome = useStorageMap('view_area_welcome')
const showMessage = computed(() => !viewAreaWelcome.get(area))
const onCloseMessage = () => {
viewAreaWelcome.set(area)
}
传入 value
如果需要记录更多状态,可以在 set
方法传入 value
:
js
const onCloseMessage = () => {
viewAreaWelcome.set(area, {
value: {
viewed: true,
...其它信息,
},
})
}
const onCloseMessage = () => {
viewAreaWelcome.set(area, {
value: {
viewed: true,
...其它信息,
},
})
}
过期时间
如果需要设定时间范围,比如一天之内只显示一次,我们也可以指定过期时间:
js
const onCloseMessage = () => {
viewAreaWelcome.set(area, {
value: {
viewed: true,
...其它信息
},
// 绝对时间戳
expiredAt: dayjs().add(1, 'day').unix(),
// 或者用相对时间(毫秒)
expiresIn: 7200
})
}
const onCloseMessage = () => {
viewAreaWelcome.set(area, {
value: {
viewed: true,
...其它信息
},
// 绝对时间戳
expiredAt: dayjs().add(1, 'day').unix(),
// 或者用相对时间(毫秒)
expiresIn: 7200
})
}
如果当前记录已过期,get
方法将自动清除数据并返回空。
TIP
使用 useStorageMap
主要在处理一系列数据时具有优势。如果你只需要存储单个数据,那么你应该使用另一个 API,即 storage。值得一提的是,storage
也支持设置过期时间。
常见问题
本地存储共享状态
在项目中,经常出现一种常见需求:某个弹窗已经显示过,不再需要弹出,而这个弹窗存在于多个页面中。对于这种情况,useStorageMap
并不适用。我们建议使用 storage
和 pinia
处理。
以下是示例代码:
js
import {defineStore} from 'pinia'
import {ref} from 'vue'
import {storage} from '@ifanrx/uni-mp'
const key = 'preview_enable_test_shared'
export default defineStore('shared-storage', () => {
const enable = ref(storage.getItem(key) || false)
const onToggle = () => {
enable.value = !enable.value
storage.setItem(key, enable.value)
}
return {
enable,
onToggle,
}
})
import {defineStore} from 'pinia'
import {ref} from 'vue'
import {storage} from '@ifanrx/uni-mp'
const key = 'preview_enable_test_shared'
export default defineStore('shared-storage', () => {
const enable = ref(storage.getItem(key) || false)
const onToggle = () => {
enable.value = !enable.value
storage.setItem(key, enable.value)
}
return {
enable,
onToggle,
}
})
vue
<script setup>
import {router, storage} from '@ifanrx/uni-mp'
import {storeToRefs} from 'pinia'
import useSharedStorage from './use-shared-storage'
const sharedStorage = useSharedStorage()
const {enable: enableShared} = storeToRefs(sharedStorage)
const {onToggle: onToggleShared} = sharedStorage
const navToSub = () => {
router.push('pages/mplib-storage/sub-page')
}
</script>
<template>
<mp-nav-bar title="storage" />
<view class="page">
<uni-section title="跨页面共享存储状态(可监听)" type="line">
<view class="example-body">
<view>
<switch :checked="enableShared" @change="onToggleShared" />
开启通知
</view>
<button class="btn" @click="navToSub">进入子页面</button>
</view>
</uni-section>
</view>
</template>
<style scoped>
.page {
padding-bottom: 100rpx;
}
.example-body {
padding: 10rpx;
}
.btn {
margin-top: 20rpx;
}
</style>
<script setup>
import {router, storage} from '@ifanrx/uni-mp'
import {storeToRefs} from 'pinia'
import useSharedStorage from './use-shared-storage'
const sharedStorage = useSharedStorage()
const {enable: enableShared} = storeToRefs(sharedStorage)
const {onToggle: onToggleShared} = sharedStorage
const navToSub = () => {
router.push('pages/mplib-storage/sub-page')
}
</script>
<template>
<mp-nav-bar title="storage" />
<view class="page">
<uni-section title="跨页面共享存储状态(可监听)" type="line">
<view class="example-body">
<view>
<switch :checked="enableShared" @change="onToggleShared" />
开启通知
</view>
<button class="btn" @click="navToSub">进入子页面</button>
</view>
</uni-section>
</view>
</template>
<style scoped>
.page {
padding-bottom: 100rpx;
}
.example-body {
padding: 10rpx;
}
.btn {
margin-top: 20rpx;
}
</style>
vue
<script setup>
import {storeToRefs} from 'pinia'
import useSharedStorage from './use-shared-storage'
const sharedStorage = useSharedStorage()
const {enable: enableShared} = storeToRefs(sharedStorage)
const {onToggle: onToggleShared} = sharedStorage
</script>
<template>
<mp-nav-bar title="storage" />
<view class="page">
<uni-section title="跨页面共享存储状态(可监听)" type="line">
<view class="example-body">
<view>
<switch :checked="enableShared" @change="onToggleShared" />
开启通知
</view>
</view>
</uni-section>
</view>
</template>
<style scoped>
.page {
padding-bottom: 100rpx;
}
.example-body {
padding: 10rpx;
}
</style>
<script setup>
import {storeToRefs} from 'pinia'
import useSharedStorage from './use-shared-storage'
const sharedStorage = useSharedStorage()
const {enable: enableShared} = storeToRefs(sharedStorage)
const {onToggle: onToggleShared} = sharedStorage
</script>
<template>
<mp-nav-bar title="storage" />
<view class="page">
<uni-section title="跨页面共享存储状态(可监听)" type="line">
<view class="example-body">
<view>
<switch :checked="enableShared" @change="onToggleShared" />
开启通知
</view>
</view>
</uni-section>
</view>
</template>
<style scoped>
.page {
padding-bottom: 100rpx;
}
.example-body {
padding: 10rpx;
}
</style>