目录

46.数据缓存Storage

7.4 数据缓存Storage

相比于临时文件、文件缓存,小程序的数据缓存可以维持的时间存储的时间更长,除非用户主动删除小程序,数据缓存里面的数据就一直有效;数据缓存的空间也有10M的大小,可以用来存储用户的阅读记录、购买记录、浏览记录、登录信息等比较关键的信息,增强用户的体验。

点击事件生成的事件对象也好,使用数据表单提交的数据也好,还是上传下载的图片、文件也好,这些数据和文件如果不保存到文件缓存或数据缓存里,只要我们重新编译小程序,这些数据都会消失。

7.4.1 将图片存储到缓存里

在了解logs的数据缓存之前,我们先来看一个将上传的图片由临时文件保存到缓存的案例,使用开发者工具在file.wxml里输入以下代码:

<view>临时文件的图片</view>
<image mode="widthFix" src="{{tempFilePath}}" style="width:100px"></image>
<view>缓存保存的图片</view>
<image mode="widthFix" src="{{savedFilePath}}" style="width:100px"></image>
<button  bindtap="chooseImage">请选择文件</button>
<button  bindtap="saveImage">保存文件到缓存</button>

然后在file.js的data里初始化临时文件的路径tempFilePath和本地缓存的路径savedFilePath:

data: {
  tempFilePath: '',
  savedFilePath: '',
},

再在file.js里添加事件处理函数chooseImage和saveImage(函数名有别于之前的chooseImg和saveImg,不要弄混了哦):

chooseImage:function() {
  const that = this
  wx.chooseImage({
    count: 1,
    success(res) {
      that.setData({
        tempFilePath: res.tempFilePaths[0]
      })
    }
  })
},
saveImage:function() {
  const that = this
  wx.saveFile({
    tempFilePath: this.data.tempFilePath,
    success(res) {
      that.setData({
        savedFilePath: res.savedFilePath
      })
      wx.setStorageSync('savedFilePath', res.savedFilePath)
    },
  })
},

我们还需要在file.js的onLoad生命周期函数里将缓存里存储的路径赋值给本地缓存的路径savedFilePath:

onLoad: function (options) {
  this.setData({
    savedFilePath: wx.getStorageSync('savedFilePath')
  })
},

编译之后,点击请上传文件的button,会触发chooseImage事件处理函数,然后调用上传图片的API wx.chooseImage,这时会将图片上传到临时文件,并将取得的临时文件地址赋值给tempFilePath,有了tempFilePath,图片就能渲染出来了。

然后再点击保存文件到缓存的button,会触发saveImage事件处理函数,然后保存文件API wx.saveFile,将tempFilePath里的图片保存到缓存,并将取得的缓存地址赋值给savedFilePath(注意tempFilePath也就是临时路径是保存文件的必备参数),这时缓存保存的图片就渲染到页面了。然后会再来调用缓存API wx.setStorageSync(),将缓存文件的路径保存到缓存的key savedFilePath里面。有些参数名称相同但是含义不同,这个要注意

通过wx.setStorageSync()保存到缓存里的数据,可以使用wx.getStorageSync()获取出来,我们在onLoad里把获取出来的缓存文件路径再赋值给savedFilePath。编译页面,看看临时文件与缓存文件的不同,临时文件由于小程序的编译会被清除掉,而缓存文件有10M的空间,只要用户不刻意删除,它就会一直在。

注意:打开开发者工具调试面板的Storage标签页,小程序的缓存记录都会在这里可以直观的看到,调试时可以留意,这一点非常重要。

7.4.2 将数据存储到缓存里

使用开发者工具新建的模板小程序里(不使用云开发服务),有一个日志logs页面,这个日志logs虽然简单,但是包含着非常复杂的JavaScript知识,是一个非常好的学习参考案例,这里我们来对它进行一一解读。

1、模块化与引入模块

在实际开发中,日期、时间的处理经常会使用到,但是使用Date对象所获取到的时间格式与我们想要展现的形式是有非常大的差异的。这时我们可以把时间的处理抽离成为一个单独的 js 文件比如util.js(util是utility的缩写,表示程序集,通用程序等意思),作为一个模块。

把通用的模块放在util.js或者common.js,把util.js放在utils文件夹里等就跟把css放在style文件夹,把页面放在pages文件夹,把图片放在images文件夹里是一样的道理,尽管文件夹或文件的名称你可以任意修改,但是为了代码的可读性,文件结构的清晰,推荐大家采用这种一看就懂的方式。

使用开发者工具在小程序根目录新建一个utils文件夹,再在文件夹下新建util.js文件,在util.js里输入以下代码(也就是参考模板小程序的logs页面调用的util.js)

const formatTime = date => {
  const year = date.getFullYear()  //获取年
  const month = date.getMonth() + 1  //获取月份,月份数值需加1
  const day = date.getDate()  //获取一月中的某一天
  const hour = date.getHours() //获取小时
  const minute = date.getMinutes()  //获取分钟
  const second = date.getSeconds() //获取秒
 
  return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')  //会单独来讲解这段代码的意思
}
 
const formatNumber = n => {  //格式化数字
  n = n.toString()
  return n[1] ? n : '0' + n 
}
 
module.exports = {  //模块向外暴露的对象,使用require引用该模块时可以获取
  formatTime: formatTime,
  formatNumber: formatNumber
}

我们再来在file.js里调用这个模块文件util.js,也就是在file.js的Page()对象前面使用require引入util.js文件(需要引入模块文件相对于当前文件的相对路径,不支持绝对路径)

const util = require('../../utils/util.js')

然后再在onLoad页面生命周期函数里打印看看这段时间处理的代码到底做了什么效果,这里也要注意调用模块里的函数的方式。

onLoad: function (options) {
  console.log('未格式化的时间',new Date())
  console.log('格式化后的时间',util.formatTime(new Date()))
  console.log('格式化后的数值',util.formatNumber(9))
},

util.formatTime()就调用了模块里的函数,通过控制台打印的日志可以看到日期时间格式的不同,比如:

未格式化的时间 Mon Sep 02 2019 11:25:18 GMT+0800 (中国标准时间)
格式化后的时间 2019/09/02 11:25:18

显然格式化后的日期时间的展现形式更符合我们的日常习惯,而9这个数值被转化成了字符串”09″。那这段格式化日期时间的代码是怎么实现的呢?这里就涉及到高阶函数的知识,一般函数调用参数,而高阶函数会调用其他函数,也就是把其他函数作为参数。

2、map的应用

相信格式化数字的代码比较好理解,如果是15日里的15,由于n[1]是15的第二位数字5,为true会直接return返回n,也就是15;比如9月里的数字9,n[1]不存在,也就是没有第二位数,于是执行 '0' + n给它加一个0,变成09;而formatNumber是一个箭头函数。

const formatNumber = n => {  //格式化数字
  n = n.toString() //将数值Number类型转为字符串类型,不然不能拼接
  return n[1] ? n : '0' + n //三目运算符,如果字符串n第2位存在也就是为2位数,那么直接返回n;如果不存在就给n前面加0
}

而格式化日期时间则涉及到map,比如下面的这段代码就有map,

return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')

map也是一个数据结构,它背后的知识非常复杂,但是我们只需了解它是做什么的就可以,如果你想对数组里面的每一个值进行函数操作并且返回一个新数组,那你可以使用map

上面这段代码就是对数组[year, month, day][hour, minute, second]里面的每一个数值都进行格式化数字的操作,这一点我们可以在file.js的onLoad里打印看效果就明白了:

onLoad: function (options) {
  console.log('2019年9月2日map处理后的结果', [2019,9,2].map(util.formatNumber))
  console.log('上午9点13分4秒map处理后的结果', [9, 13, 4].map(util.formatNumber))
},

从控制台打印的结果就可以看到数组里面的数字被格式化处理,有两位数的不处理,没有2位数的前面加0,而且返回的也是数组。至于数组Array的join方法,就是将数组元素拼接为字符串,以分隔符分割,上面[year, month, day]分隔符为/[hour, minute, second]的分隔符为:

3、将数据存储到缓存里

我们再回头看logs的缓存案例,在小程序app.js的生命周期函数onLaunch里输入以下代码,也就是在小程序初始化的时候就执行日志进行记录:

//  ||为逻辑与,就是声明logs为获取缓存里的logs记录,没有时就为空数组
var logs = wx.getStorageSync('logs') || []

//unshift()是数组的操作方法,它会将一个或多个元素添加到数组的开头,这样最新的记录就放在数组的最前面,
//这里是把Date.now()获取到的时间戳放置到数组的最前面
logs.unshift(Date.now())

//将logs数据存储到缓存指定的key也就是logs里面
wx.setStorageSync('logs', logs)
console.log(logs)
console.log(Date.now())

当我们不断编译,logs数组里面的记录会不断增加,增加的值都是时间戳。那如何把缓存里面的日志给渲染到页面呢?

在file.wxml里添加以下代码,由于logs是数组,我们使用列表渲染,这里有个数组的index值,由于index是从0开始记录,给index加1,符合我们日常使用习惯。

<view wx:for="{{logs}}" wx:for-item="log">
  <view>{{index + 1}}. {{log}}</view>
</view>

然后在file.js的data里初始化logs

data: {
  logs: []
},

然后再在file.js的生命周期函数onLoad里把缓存里的日志取出来通过setData赋值给data里的logs

onLoad: function () {
  this.setData({
    logs: (wx.getStorageSync('logs') || []).map(log => {
      return util.formatTime(new Date(log))
    })
  })
},

结合前面所了解的map、模块化知识就不难理解上面的这段代码了。缓存有同步API和异步API的区别,结合之前我们了解的同步和异步的知识,看看缓存的同步API与异步API的区别。

缓存的好处非常多,比如用户的浏览文章、播放视频的进度(看了哪些文章,给个特别的样式,免得用户不知道看到哪里了)、用户的登录信息(用户登录一次,可以很长时间不用再登录)、自定义的模板样式(用户选择自己喜欢的样式,下次打开小程序还是一样)、最经常使用的小图片(保存在缓存,下次打开小程序速度更快)等等。