路由管理

在路由切换中添加逻辑处理

整体代码

// Some code
const { location, history } = window

const createHistoryStateStore = (state = location.href)=>{
  const getState = ()=>{
    if (listenerSet.size === 0) throw new Error('should subscribe before getState')
    return state
  }
  const setState = (nextState, shouldPushState) =>{ //新路由和状态
    if (typeof (nextState) !== 'string') throw new Error(`non-string href: ${nextState}`) 
    if (nextState === state) return state //新路由和当前路由一样 不做处理
    console.log('do set nextState:',nextState, 'shouldPushState:',shouldPushState,'state:',state)
    const prevState = state //把当前存储的路由状态当做旧路由
    state = nextState//传入的新路由为当前路由状态
    shouldPushState !== 'skip' && history.pushState(null, '', state) //判断操作性质,决定是否需要手动更新histroy栈
    listenerSet.forEach((listener) => listener(state, prevState)) //执行路由变更处理逻辑
    return state
  }
  const listenerPopState = () => { setState(location.href, 'skip') } //原生浏览器操作时才触发
  const listenerHashChange = () => { setState(location.href) } // js调用history.pushState()或history.replaceState() 或者直接改地址框地址 记录
  const listenerSet = new Set() //存储路由变更时执行逻辑的函数集合
  const subscribe = (listener) => { //绑定事件监听
    listenerSet.add(listener) //存储处理函数
    if (listenerSet.size !== 1) return //如果以前已经添加过监听处理,无需重复绑定
    window.addEventListener('popstate', listenerPopState) //原生流量器操作
    window.addEventListener('hashchange', listenerHashChange) // js操作
  }
  const unsubscribe = (listener) => {
    listenerSet.delete(listener)// 删除存储处理函数 
    if (listenerSet.size !== 0) return //如果删除某个处理函数后仍存在其他处理逻辑则不需解绑
    window.removeEventListener('popstate', listenerPopState) 
    window.removeEventListener('hashchange', listenerHashChange)
  }

  return { getState, setState, subscribe, unsubscribe }
}
export { createHistoryStateStore } 


// e.g:

var his = createHistoryStateStore()
his.subscribe((state, prevState)=>{ console.log('state:',state, 'prevState:',prevState)})
  • 用 listenerSet做集合可以维护多套处理逻辑,对应的要注意绑定和取消监听逻辑

  • 核心逻辑内始终只维护当前页面的href信息,和页面变更后传入的新页面href信息,以此提供给处理函数

  • 唯一的要注意的是要不要自己进行history.pushState调用,浏览器自发处理不需要,JS调用需要

  • 实际上路由栈还是靠浏览器自己维护的路由栈信息

popstate

当活动历史记录条目更改时,将触发popstate事件。如果被激活的历史记录条目是通过对history.pushState()的调用创建的,或者受到对history.replaceState()的调用的影响,popstate事件的state属性包含历史条目的状态对象的副本。

需要注意的是调用history.pushState()history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()或者history.forward()方法)

不同的浏览器在加载页面时处理popstate事件的形式存在差异。页面加载时Chrome和Safari通常会触发(emit )popstate事件,但Firefox则不会

History.pushState()

history.pushState(state, title,[ url])

state状态对象是一个JavaScript对象,它与pushState()创建的新历史记录条目相关联。 每当用户导航到新状态时,都会触发popstate (en-US)事件,并且该事件的状态属性包含历史记录条目的状态对象的副本。状态对象可以是任何可以序列化的对象。 因为Firefox将状态对象保存到用户的磁盘上,以便用户重新启动浏览器后可以将其还原,所以我们对状态对象的序列化表示施加了640k个字符的大小限制。 如果将序列化表示形式大于此状态的状态对象传递给pushState(),则该方法将引发异常。 如果您需要更多空间,建议您使用 sessionStorage或者localStoragetitle当前大多数浏览器都忽略此参数,尽管将来可能会使用它。 在此处传递空字符串应该可以防止将来对方法的更改。 或者,您可以为要移动的状态传递简短的标题。url 可选新历史记录条目的URL由此参数指定。 请注意,浏览器不会在调用pushState() 之后尝试加载此URL,但可能会稍后尝试加载URL,例如在用户重新启动浏览器之后。 新的URL不必是绝对的。 如果是相对的,则相对于当前URL进行解析。 新网址必须与当前网址相同 origin; 否则,pushState()将引发异常。 如果未指定此参数,则将其设置为文档的当前URL。

最后更新于