如何设计一个移动端支持返回键的 Vue 组件

原创
前端路迹
2021-7-8 10:24
编辑于 2022-6-17 16:39

在一些复杂的 Vue 组件中,组件需要根据不同的场景显示不同的视图,视图切换要支持返回键返回到上一个视图,如果使用 vue-rouer,组件就得和view一样挂在一个路径下,的确可以很好解决返回的问题,但是调用组件变成了跳转页面,如果是一个表单页面,那么跳转之前还得临时保存表单的数据,显然这个思路不符合组件的思想。最容易想到的就是 hashchange 或 pushState ,由于可能会和 vue-router 产生冲突,也不是很好的方案。

组件要支持返回,那么肯定需要在浏览历史记录上做变动,前端路由的基础上如何在做历史记录的变动呢?答案就是使用改变路由参数的方式。

驱动视图改变不再是直接给参数赋值,而是通过路由参数的改变。视图改变值也由监听 $route.query 的变化来获取,经过简单封装,代码如下。

<template>
    <div>
        <div class="page" v-if="page==='page1'">
            当前page1<br><br>
            <button @click="goPage('page2')">跳转到 page2</button>
        </div>
        <div class="page" v-else-if="page==='page2'">
            当前page2<br><br>
            <button @click="goPage('page3')">跳转到 page3</button>
        </div>
        <div class="page" v-else-if="page==='page3'">
            当前page3<br><br>
            <button @click="goPage('page4')">跳转到 page4</button>
        </div>
        <div class="page" v-else-if="page==='page4'">
            当前page4<br><br>
        </div>
        <div class="page" v-else>
            当前起始页<br><br>
            <button @click="goPage('page1')">跳转到 page1</button>
        </div>
    </div>
</template>

<script>
export default {
    watch: {
        '$route.query': {
            handler(newVal) {
                if (newVal.__component_id__ === this.id) {
                    this.page = newVal.__component_page__.replace(this.id, '')
                }
            }
        }
    },
    data() {
        return {
            id: Math.random().toString(16).slice(2),
            page: ''
        }
    },
    methods: {
        goPage(page) {
            const route = this.$route
            this.$router.push({
                path: route.path,
                query: {
                    ...route.query,
                    __component_id__: this.id,
                    __component_page__: this.id + page
                }
            })
        }
    }
}
</script>

<style lang="less">
.page {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
}
</style>

为何参数需要拼接id?

这个id主要是为了解决在某一个视图下刷新页面,无法区分是否为当前组件下的参数,id是组件创建时生成,刷新后,id会变化,进而不会触发内部的page变化。

浏览历史记录变化伴随着一个问题,那就是不断地前进后退,会导致历史记录不断增多,如果管理不好,就会出现无法一步退出到起始页面的问题。因此在这种方案下,需要自己做一个历史记录的管理。

goPage(page, replace) {
            const route = this.$route
            const index = this.history.indexOf(page)
            if (index > -1) {
                const len = this.history.length
                this.history.splice(index + 1, len)
                history.go(-(len - (index + 1)))
                return
            }
            if (replace) {
                this.history.splice(this.history.length - 1, 1, page)
            } else {
                this.history.push(page)
            }
            this.$router[replace ? 'replace' : 'push']({
                path: route.path,
                query: {
                    ...route.query,
                    __component_id__: this.id,
                    __component_page__: this.id + page
                }
            })
        }

每一次改变page,将page记录到history中,我这里做了一个处理,针对历史记录中存在相同的page,则采用返回机制,而不是跳转,这个可以解决用户连续返回时可以快速返回到起始页,否则由于历史记录一直累加,用户需要点击非常多次返回才能返回到起始页。

上面的代码只考虑了进入不同的page的情况,当用户点击返回键时,这时候需要把history中对应的page给删除掉,保证history的正确性。

watch: {
    '$route.query': {
        handler(newVal, oldVal) {
            if (newVal.__component_id__ === this.id) {
                this.page = newVal.__component_page__.replace(this.id, '')
                const lastPage = oldVal.__component_page__.replace(this.id, '')
                if (lastPage === this.history[this.history.length - 1]) {
                    this.history.pop()
                }
            } else {
                this.page = ''
                this.history = []
            }
        }
    }
},

由于组件为了一套page的浏览记录,因此在任何组件的视图下返回到起始页就变得很简单。

goBack() {
    history.go(-this.history.length)
    this.history = []
}

最后总结一下整个组件的核心思路:

转载请注明出处。本文地址: https://www.qinshenxue.com/article/vue-mobile-support-back-compnent.html
关注我的公众号