1

一、路由滚动行为处理

默认情况下,当页面中有滚动条的时候,我们通过vue-router进行页面的切换时,你会发现滚动条的位置就像会被缓存了一样,但是通过created钩子,我们可以看到在切换路由的过程中,组件是在不断创建和销毁的,但是滚动条位置会保持和上一个页面一致。但是有的时候我们想要在切换路由的时候,希望目标路由页面滚动条在最上面,那么我们可以通过scrollBehavior进行处理,这是创建Router对象时传递的配置对象中的一个属性,值为一个函数,如:

export default new Router({
    scrollBehavior(to, from, savedPosition) {
        if (savedPosition) { // 浏览器前进后退按钮切换路由
            return savedPosition;
        } else { // 通过<router-link>切换
            return {x: 0, y: 0};
        }
    }
});

scrollBehavior函数中的savedPosition值,如果是通过浏览器的前进后退按钮切换路由,那么savedPosition值不为null;如果是通过<router-link>进行路由切换,那么savedPosition值为null,此时就会回到目标路由页面的顶部,只有通过浏览器前进后退按钮进行切换才能保持之前的滚动条位置

二、定制不同组件的scrollBehavior

我们可以通过路由的元信息meta更细颗粒度控制滚动行为,即在App.vue组件mounted的时候给window添加onscroll事件,然后监听滚动条位置,并且将位置保存到当前路由的meta元信息对象中,最后在Router对象的scrollBehavior中返回目标路由的meta元信息对象中滚动条位置即可。

// App.vue
export default {
    data() {
        return {
            timerId: -1
        }
    },
    mounted () {
        window.onscroll = this.doScroll;
    },
    methods: {
        doScroll() {
            if (this.timerId) clearTimeout(this.timerId);
            this.timerId = setTimeout(() => {
                // 获取页面滚动距离之后设置给当前路由的元信息
                this.$route.meta.y = window.pageYOffset;
            }, 300);
        }
    }
}
export default new Router({
    scrollBehavior(to, from, savedPosition) {
        return to.meta; // 返回目标路由的元数据
    }
});

此时不管是通过浏览器前进后退按钮切换还是通过<router-link>切换路由都能保持之前的滚动条位置了。

三、组件内监听路由变化

当路由目标组件被缓存后,即使用了<keep-alive>包裹,那么组件的created钩子就只会被执行一次。比如有一个商品详情页面被缓存了,每当从列表页面进入详情页面的时候,我们只需要通过路由传递一个商品id,然后在created钩子内调用方法根据传递的商品id向服务器发起请求获取对应的商品详情信息,但是由于商品详情页面被缓存了,所以即使路由传递的商品id发生了变化,但是created钩子未能执行,导致商品信息并未发生变化,如:

export default {
    created() {
        this.getInfo(); // 当前组件被缓存,只会被执行一次
    },
    methods: {
        getInfo(){
            this.$api.get("http://localhost:3000/info", this.$route.query.id);
        }
    }
}

解决办法有以下几种,如:

  • 在actived钩子内请求数据,如:
export default {
    actived() { // 每次进入缓存组件都会执行active钩子
        this.getInfo();
    }
}
  • 通过watch监听当前路由变化,如:
export default {
    watch: {
        $route(to, from) { // to相当于是新值,from相当于是旧值
            if(to.path === "/detail") { // 如果进入了当前组件
                this.getInfo();
            }
        }
    }
}
  • 通过组件级路由钩子beforeRouteEnter,如:
export default {
    beforeRouteEnter(to, from, next) {
        console.log(this); // 这里组件实例还未创建为undefined
        if (to.path === "/detail") {
            next((vm) => { // 回调函数内会被传入组件实例对象
                vm.getInfo();
            });
        } else {
            next();
        }
    }
}

需要注意的是,路由钩子的执行顺序在组件实例创建之前,所以执行到路由钩子的时候组件还未创建,这个时候this值为undefined,要想路由钩子内能访问到路由组件实例对象,那么可以在next中传递一个回调函数,等组件实例对象创建好之后就会传递给回调函数了,这个时候再调用组件内的方法即可。

四、实现从详情页返回列表页缓存数据和浏览位置,从其他页面进入到列表页则刷新数据的功能

要实现从详情页返回到列表页面能够缓存之前的数据和浏览位置,那么我们需要将List组件设置为可缓存,然后再定义一个是否使用缓存数据的变量来控制List组件数据是否需要刷新,如:

{
    path: "/list",
    component: List,
    meta: {
        keepAlive: true, // 该组件是否需要缓存,默认缓存
        isUseCache: false, // 是否需要刷新数据,默认刷新
    }
}

然后我们可以根据List组件的keepAlive属性来定义该组件是否需要添加上<keep-alive>进行缓存了,如:

<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>

接下来就是处理数据是否缓存了,我们可以在actived钩子中进行处理,默认进入List组件都是要刷新数据的,如:

actived() {
    if (!this.$route.meta.isUseCache) { // 如果不使用缓存
        this.list = [];
        this.loadList(); // 刷新数据
    }
}

现在从任何页面进入到List页面都会刷新数据,但是我们希望从详情页面进入到List页面不刷新数据,那么当从详情页面进入到List页面的时候,isUseCache值为true,所以我们可以在List页面的beforeRouteLeave()钩子中进行处理,即在离开List页面的时候进行判断,如果离开列表页面进入的是详情页面,那么就将isUseCache的值设置为true;如果离开列表页面进入的不是详情页面,那么isUseCache的值设置为false,再次进入列表页面的时候就会自动刷新数据了,如:

// 列表页面的beforeRouteLeave钩子
beforeRouteLeave(to, from, next) {
    if (to.name === "Detail") {
        from.meta.isUseCache = true; // 离开列表页进入详情页isUseCache为true
    } else {
        from.meta.isUseCache = false; // 离开列表页进入非详情页面isUseCache为false
    }
    next();
}

当然,我们也可以在actived钩子的最后将isUseCache设置为false,即每次进入List页面都将isUseCache设置为false,两种方法都可以,一种是每次进入设置为false,另一种是每次离开的时候设置为false。


JS_Even_JS
2.6k 声望3.7k 粉丝

前端工程师