如果你是大佬,拉到最下面,解答一下我的困惑,真查不到没头绪
问题:由于小程序首页,用户访问并发量高时或网络不佳时,用户体验有明显卡顿。
解决思路:由于首页存在多个列表,图片资源较多,从图片缓存进行尝试。
小程序自带的图片缓存
在微信小程序中,如果同一个图片资源在页面中多次使用,它只会被加载一次,并且会被缓存起来。当页面中再次使用相同的图片资源时,不会重新发送网络请求,而是直接使用缓存的图片资源。这种行为是由微信客户端自动处理的,它会根据图片的 URL 进行缓存,以提高图片的加载速度和节省网络流量。当页面中同时使用多个相同的图片资源时,微信客户端会自动判断并复用已经缓存的图片,避免重复加载。
原有小程序图片缓存设计(getImageUrl)
- 利用内存缓存储存一个缓存时间
- 将这个缓存时间拼接到图片url后面进行请求
- 每次请求图片判断当前时间和缓存时间是否有十分钟,有十分钟替换当前时间为缓存时间,拼接请求
存在问题:每次刷新小程序,重新进入等,图片资源再次被请求,无法有效缓解首页卡顿问题
基于微信文件系统的静态资源缓存
- 利用微信本地缓存存储缓存时间,缓存时间不会随刷新,重新进入等操作重新赋值
// 设置图片缓存时间 if (!wx._getStorageSync('imageCacheTime')) { wx._setStorageSync('imageCacheTime', new Date().getTime()) }
- 将缓存时间拼接到图片路径后,首次加载完成后,利用文件管理系统对图片路径进行加密本地缓存,重新进入或刷新进入后读取本地文件管理系统中的图片缓存。
// export function getEncryption(key) { const encrypt = md5(key) return encrypt } //href:图片路径 isClear:是否清空目录下所有缓存 export function setCacheImages(href, isClear) { return new Promise((resolve, reject) => { try { //截取图片路径唯一的部分 const realUrl = href.split('?')[1] // 生成加密唯一key const fileIdentifier = getEncryption(realUrl) //获取文件管理系统实例 const fileManager = wx.getFileSystemManager() // 读取指定文件夹下缓存的所有文件 const dir = fileManager.readdirSync(wx.env.USER_DATA_PATH) //如果缓存超时清空文件管理文件夹 if (isClear) { dir.forEach(file => { if (file !== 'miniprogramLog') { fileManager.unlinkSync(`${wx.env.USER_DATA_PATH}/${file}`) } }) } // 检查是否存在缓存文件 const cachedFile = dir.find(item => item.indexOf(fileIdentifier) > -1) //有缓存返回对应文件路径,没缓存请求下载 if (cachedFile) { resolve(`${wx.env.USER_DATA_PATH}/${cachedFile}`) } else { downloadCanchFile(href, fileIdentifier) .then(response => { resolve(response) }) .catch(() => { // 下载失败可能是网络问题,返回原始图片链接 reject(href) }) } } catch (error) { reject(href) } resolve(href) }) } //图片下载 function downloadCanchFile(url, fileIdentifier) { // 使用Set存储允许的文件扩展名 const allowedImageExtensions = new Set([ 'bmp', 'jpg', 'jpeg', 'jpe', 'jxr', 'png', 'tif', 'tiff', 'avif', 'xbm', 'pjp', 'svgz', 'ico', 'svg', 'jfif', 'webp', 'pjpeg', 'gif', 'iff', 'ilbm', 'ppm', 'pcx', 'xcf', 'xpm', 'psd', 'mng', 'sai', 'psp', 'ufo' ]) return new Promise((resolve, reject) => { wx.downloadFile({ url, success(downloadRes) { //下载成功后使用生成的唯一key,将图片缓存到指定目录下 if (downloadRes.statusCode === 200) { const imageType = downloadRes.tempFilePath.split('.').pop()?.toLowerCase() if (imageType && allowedImageExtensions.has(imageType)) { const cancheFilePath = `${wx.env.USER_DATA_PATH}/${fileIdentifier}.${imageType}` const FileManager = wx.getFileSystemManager() FileManager.saveFile({ tempFilePath: downloadRes.tempFilePath, filePath: cancheFilePath, success(saveResult) { if (saveResult.errMsg === 'saveFile:ok') { resolve(saveResult.savedFilePath) } else { reject(new Error('Failed to save file.')) } }, fail() { reject(new Error('Failed to save file.')) } }) } else { reject(new Error('Unsupported file type.')) } } else { reject(new Error('Failed to download file. Status code: ' + downloadRes.statusCode)) } }, fail(error) { reject(new Error('Download failed: ' + error.errMsg)) } }) }) }
创建公共方法并使用
export const getCacheImageUrl = async ({ ids, isWater, watermark = '', width, height }) => {
if (!ids) return ''
let cacheTime = wx._getStorageSync('imageCacheTime')
// 更新缓存时间
let curTime = new Date().getTime()
let isClear = curTime - cacheTime > 600000
if (isClear) wx._setStorageSync('imageCacheTime', curTime)
// baseUrl
const baseUrl = getBaseUrl({ serviceName: 'dfs' })
// size
const size = width && height ? `&isComp=Y&width=${width}&height=${height}` : ''
// isWater
isWater = isWater ? '&isWater=Y' : ''
// watermark
watermark = watermark ? `&watermark=${watermark}` : ''
// 返回 url
if (typeof ids === 'string') {
return await setCacheImages(
`${baseUrl}/attach/unAuth/view?id=${ids}${size}${isWater}${watermark}&time=${cacheTime}`,
isClear
)
} else if (Array.isArray(ids)) {
return ids.map(async id => {
return await setCacheImages(
`${baseUrl}/attach/unAuth/view?id=${id}${size}${isWater}${watermark}&time=${cacheTime}`,
isClear
)
})
} else {
return []
}
}
提问:我将这个方法放在组件的observers中调用,不加setTimeout,而是在function (projectItem)前加async,同时在外面使用list,wx:for同时渲染多个这个组件,假如同时渲染十个,会出现这十个中的异步都走完才会同时显示,而不是list拿到后就显示,然后图片一个一个出来,看起来就行它将这十个异步同步执行了,第一个阻塞第二个这样,这是为什么?这样加了setTimeout就好了,代码中写的原因官方文档并没有说明,只是查出来的不确定到底对不对。有无大佬解释一下是什么原因?
observers: {
projectItem: function (projectItem) {
if (projectItem.pics && projectItem.pics.length) {
if (this.properties.cache) {
//在小程序中,多个组件的异步函数在observers中执行时,单线程特性,异步函数的执行仍然是按顺序进行的,无法实现并行渲染。这是因为observers是依赖于数据变化进行触发的,而它们的执行顺序和执行效率是由小程序的事件循环机制决定的,不能手动控制
//当多个相同的组件同时渲染时,每个组件的渲染是在同一个线程上进行的。当一个组件的 observers 中的同步函数执行时,会阻塞当前线程的执行,包括后续组件的渲染。
//setTimeout临时解决方案:为解决当在组件内部调用异步函数并且同时多个相同组件同时加载时,多个异步同步进行阻塞渲染问题
setTimeout(async () => {
const imageSrc = await getCacheImageUrl({
ids: projectItem.pics[0],
envVersion,
width: 500,
height: 360
})
this.setData({
lastImage: imageSrc
})
}, 0)
}
}
},