思索-js 页面ID识别及数据缓存

思索-页面ID识别及数据缓存

页面 ID 识别的思路

多页应用在移动端是较为常见的一种架构,它可以和APP 内的 webview 配合,达到类似原生的体验,这一点是单页应用无法做到的(比如手势滑动等,会直接关闭 webview)。

多页应用中,使用location 进行跳转时页面会被销毁,页面后退或刷新时,页面就会是一个全新的加载,在一些需要页面级的缓存数据时,没办法通过简单的 sessionStorage 进行缓存,主要是因为 sessionStorage 生命周期存在一个上下文,与本页面关联,或者重新进入本页面的时候都会一直存在。而我们要做到的效果是:刷新/后退时页面数据缓存,重新进入页面不缓存。

为什么我们难以实现页面 ID 的识别?因为页面本身是无状态的,如果 URL 唯一,那么可以把 URL 当作是页面的 ID,如果页面入口不是自己控制,那我们没办法保证 URL 会是唯一的。想要给页面加个 ID,最先想到的是自己维护一套 history,每次生成时候保存页面 ID,根据前进或者是后退来识别当前页面位于哪个位置。

判断页面的前进后退,我们可以用浏览器提供的接口:performance.navigation.type 来判断(参见:https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming,

function getNaviagteType () {
  const { performance } = window

  // performance navigation 兼容 ios9+, Android 全部, 低于该版本默认全部是新打开
  if (performance) {
    // 新的 performance 接口
    if (typeof performance.getEntriesByType === 'function') {
      const perfEntries = performance.getEntriesByType('navigation') || []
      const [timing] = perfEntries
      if (timing && timing.type) {
        return timing.type
      }
    }

    const typeMap = ['navigate', 'reload', 'back_forward']
    // 旧的 performance 接口
    if (performance.navigation && performance.navigation.type != null) {
      return typeMap[performance.navigation.type] || 'reserved'
    }
  }
  return 'not_support'
}

根据这个来判断,我们就可以拿到需要的导航类型。这样和 sessionStorage 配合,就可以维护一套可用的历史记录版本了。但是如果我们只是要页面ID 级别的缓存呢?

页面级别数据缓存

如果只需要数据缓存,我们就不需要考虑维护 ID 了。因为正常情况下我们的历史记录里面不会出现两次一样的 URL,或者是出现也不影响我们的页面表现。具体代码如下:


const SESSION_KEY = 'page-cache'

const { sessionStorage } = window

function safeJsonParse(str, defaultValue) {
  if (!str) {
    return defaultValue;
  }
  let res
  try {
    res = JSON.parse(str)
  } catch (err) {
    // eslint-disable-next-line no-console
    console.error(err)
    res = defaultValue
  }
  return res || defaultValue
}

/**
 * 初始化页面数据
 * @param {*} cacheData 数据对象
 * @param {*} pageId 页面ID
 * @param {*} usePreData 是否使用上次缓存的值
 */
function initPageData (cacheData, pageId, usePreData) {
  const initData = cacheData
  initData.pageId = pageId
  initData.data = initData.data || {}
  initData.data[pageId] = (usePreData ? initData.data[pageId] : null) || {}

  sessionStorage.setItem(SESSION_KEY, JSON.stringify(initData))
}
/**
 * 初始化页面数据
 */
function init (pageId = window.location.pathname) {
  const cacheData = safeJsonParse(sessionStorage.getItem(SESSION_KEY), {})
  const navType = getNaviagteType()
  // 这几种情况下不清除数据,直接沿用上一次数据
  if (cacheData.pageId && ['back_forward', 'reload', 'not_support'].indexOf(navType) > -1) {
    initPageData(cacheData, pageId, true)
    return cacheData
  }

  initPageData(cacheData, pageId, false)
  return cacheData
}
init()

/**
 * 获取全量的 cache 数据
 */
function getFullCacheData () {
  const cacheData = safeJsonParse(sessionStorage.getItem(SESSION_KEY), null)
  return cacheData || init()
}
/**
 * 获取缓存中的数据
 */
function getPageCache (cacheData = getFullCacheData()) {
  return cacheData.data[cacheData.pageId] || {}
}

/**
 * 获取缓存中指定 key 的数据
 * @param {*} key key 值
 */
function getPageCacheItem (key) {
  const cacheData = getPageCache()
  return cacheData[key]
}

/**
 * 设置缓存内容
 * @param {*} key key
 * @param {*} value 对应的数据
 */
function setPageCacheItem (key, value) {
  const fullData = getFullCacheData()
  const cacheData = getPageCache(fullData)
  cacheData[key] = value
  sessionStorage.setItem(SESSION_KEY, JSON.stringify(fullData))
}

/**
 * 移除缓存中页面的某个数据
 * @param {*} key 条目名称
 */
function removePageCacheItem (key) {
  const fullData = getFullCacheData()
  const cacheData = getPageCache(fullData)
  delete cacheData[key]
  sessionStorage.setItem(SESSION_KEY, JSON.stringify(fullData))
}

/**
 * 移除缓存
 */
function clearPageCache () {
  const fullData = getFullCacheData()
  fullData.data[fullData.pageId] = {}
  sessionStorage.setItem(SESSION_KEY, JSON.stringify(fullData))
}

const cache = {
  init,
  getAll: getPageCache,
  getItem: getPageCacheItem,
  setItem: setPageCacheItem,
  removeItem: removePageCacheItem,
  clear: clearPageCache
}

该方案的主要缺陷是:

  • 不兼容 IOS9,所以需要考虑到失效情况下你的业务是什么表现
  • 历史记录里面存在相同的url,那缓存数据会产生污染

如果你有兴趣的话,可以实现一个页面 ID 的功能,在初始化的时候设置 pageId 的值就可以了。

posted @ 2020-08-22 16:27  无梦灬  阅读(255)  评论(0编辑  收藏  举报