likes
comments
collection
share

关于 location.hash 和 history Api 的一些思考

作者站长头像
站长
· 阅读数 22

废话开篇:vue下的 “路由”这个词抛开 vue 语境,除了让人们认识这个名字以外很难从字面层面去直观的理解它到底是干什么的,个人觉得很不友好,因为初学者除了能记住这个名字以外,对它其实很难进行直观的理解。单从“用途”上来说它是统一配置及管理指定组件根据“请求地址”来显示或者隐藏的功能模块,将页面的展示进行统一管理,方便单页面应用的页面转换。

一、location 的 hash 及 history 相关 Api 能做什么?

最终要实现的功能:通过浏览器或者自定义的某个特殊的标识在设置不同的值后能够将 router-view 模版内容同时替换为相应的内容。

这里有个疑问,如果自定义一个管理类,声明一个它的属性,然后在这个属性进行修改的时候,切换不同的模版内容行不行,感觉也没什么问题。但是,如果能结合浏览器自身相应的功能岂不是更好?

locationhashhistorystate 就是用来满足这个实现的标识。

1、不同的 hash

hash 的切换对于浏览器访问地址的变化

http://10.10.30.4:3000/historyApi.html#comment
http://10.10.30.4:3000/historyApi.html#image

设置不同的 hash 值只是在地址后面追加了 “#”及对应的值

2、不同的 historystate

historystate 的切换对于浏览器访问地址的变化

http://10.10.30.4:3000/comment.html
http://10.10.30.4:3000/image.html

设置不同的 state 值,使当前浏览器的“访问地址”整个都发生了变化,这里所谓的“访问地址”其实并不是真正的去请求了一个对应的服务器地址,可以理解为仅是浏览器的本地设置,所以当在 history 对象调用 pushState 方法设置不同的 state 值后,强制刷新浏览器将会出现找不到资源 404 的问题,如图:

关于 location.hash 和 history Api 的一些思考

上面的两种切换都可以通过浏览器的“前进”、“后退”按钮进行切换 locationhash 值或 historystate 值,浏览记录保存与切换就可以交给浏览器操作,后面就可以根据变化值的不同动态切换模版内容进行相应显示即可。

二、实现效果

关于 location.hash 和 history Api 的一些思考

三、代码

1、HTML用法
<body>
    <div id="hashContent">
        <div data-route="article" data-href="article.html">文章</div>
        <div data-route="image" data-href="image.html">图片</div>
        <div data-route="comment" data-href="comment.html">评论</div>
    </div>
    <!-- 模版 -->
    <router-view/>
</body>
<script type="module">
    import { HashManager ,HistoryManager} from './router.js'
    // hash
    let hashManager = new HashManager(document.getElementById('hashContent'),(template)=>{
        //替换模版
        document.getElementsByTagName('router-view')[0].innerHTML = template
    },(parentEl,currentEl)=>{
        //更改选中UI样式(不是重点)
        Array.from(parentEl.getElementsByTagName('div')).forEach((el)=>{
            el.setAttribute('selected','false')
        })
        currentEl.setAttribute('selected','true')
    })

    // history
    let historyManager = new HistoryManager(document.getElementById('hashContent'),(template)=>{
        //替换模版
        document.getElementsByTagName('router-view')[0].innerHTML = template
    },(parentEl,currentEl)=>{
        //更改选中UI样式(不是重点)
        Array.from(parentEl.getElementsByTagName('div')).forEach((el)=>{
            el.setAttribute('selected','false')
        })
        currentEl.setAttribute('selected','true')
    })
</script>
2、HashManagerHistoryManager

HashManager 类操作的是 locationhash

class HashManager{
    callBack = null
    onclickCallBack = null

    constructor(el,callBack,onclickCallBack){
        // 切换回调
        this.callBack = callBack
        // 点击事件回调
        this.onclickCallBack = onclickCallBack
        // 获取hash设置dom对象
        Array.from(el.getElementsByTagName('div')).forEach((childEl)=>{
            childEl.onclick = ()=>{
                this.onclickCallBack && this.onclickCallBack(el,childEl)
                this.hash = childEl.dataset.route
            }
        })
        // 监听window哈希变化
        window.onhashchange = this.hashChangeEvent.bind(this)
        // 默认执行一次当前window哈希值判断
        this.hashChangeEvent()
    }

    // 设置哈希值
    set hash(newValue){
        window.location.hash = '#' + newValue
    }
    
    // 哈希改变切换模版
    hashChangeEvent = ()=>{
        let hash = window.location.hash
        let template = '<div>我是404</div>'
        switch(hash){
            case '#article':{
                template = '<div>我是文章模版</div>'
            }
            break
            case '#image':{
                template = '<div>我是图片模版</div>'
            }
            break
            case '#comment':{
                template = '<div>我是评论模版</div>'
            }
            break
            default:{

            }
        }
        this.callBack && this.callBack(template)
    }
}


HistoryManager 类操作的是 history 的值

class HistoryManager{
    callBack = null
    onclickCallBack = null
    constructor(el,callBack,onclickCallBack){
        // 切换回调
        this.callBack = callBack
        // 点击事件回调
        this.onclickCallBack = onclickCallBack
        // 获取hash设置dom对象
        Array.from(el.getElementsByTagName('div')).forEach((childEl)=>{
            childEl.onclick = ()=>{
                this.onclickCallBack && this.onclickCallBack(el,childEl)
                this.currentWindowLoadAddress = childEl.dataset.href
            }
        })
        // 监听window的state变化
        window.onpopstate = this.popstateChangeEvent.bind(this)
    }

    // 设置新的window加载地址
    set currentWindowLoadAddress(newValue){
        const state = { href: newValue }
        const url = './' + newValue
        history.pushState(state, '', url)
        this.popstateChangeEvent()
    }

    // 上一页
    back(){
        history.back()
    }

    // 下一页
    forward(){
        history.forward()
    }

    // 跳转
    go(num){
        history.go(num)
    }

    // popstate变化切换模版
    popstateChangeEvent(){
        let href = history.state.href
        let template = '<div>我是404</div>'
        switch(href){
            case 'article.html':{
                template = '<div>我是文章模版</div>'
            }
            break
            case 'image.html':{
                template = '<div>我是图片模版</div>'
            }
            break
            case 'comment.html':{
                template = '<div>我是评论模版</div>'
            }
            break
            default:{
            }
        }
        this.callBack && this.callBack(template)
    }
}

export { HashManager,HistoryManager }

四、总结与思考

眼过千遍,不如手敲一遍,透过框架映射的规则理解一下原理更有助于加深理解,代码拙劣,大神勿笑[抱拳][抱拳][抱拳]