Vue全家桶要点与事项手记之 Vue Router

Vue Router

url 导航传参

模式匹配路径$route.params
/user/:username/user/evan{ username: 'evan' }
/user/:username/post/:post_id/user/evan/post/123{ username: 'evan', post_id: '123' }

通过router-link标签来选择路由跳转:

<div id="demo">
    <router-view> </router-view>
</div>

<script>
    const Home = {
        template: `
            <div>
                <h2> Home </h2>
                <router-link :to="{name: 'User', params: {name: 'foo'}}">
                    go to user page
                </router-link>
            </div>
        `
    };

    const User = {
        data: function() {
            return {
                name: ''
            }
        },
        created: function() {
            this.name = this.$route.params.name;
        },
        template: `
            <h2> Hello, {{ name }} </h2>
        `
    }

    const router = new VueRouter({
        routes: [

            // 动态路径参数 以冒号开头
            {name: 'User',path: '/user/:name', component: User},
            {path: '/', component: Home}
        ]
    });

    new Vue({
        el: "#demo",
        router: router
    });

</script>

另一种常见的参数使用query传递,如果把params方式比作post请求,则query就相当于get方式请求。

除了url形式上的区别外,对于路由的选择方式上:params根据路由的name属性来选择,而query是根据路由的path属性来选择的

路由组件属性传参

将路由设置中的props属性设置为true,使组件属性来自动接收路由参数,从而降低组件和路由的耦合

<div id="demo">
    <router-view> </router-view>
</div>

<script>
    const Home = {
        template: `
            <div>
                <h2> Home </h2>
                <router-link :to="{name: 'User', params: {name: 'foo'}}">
                    go to user page
                </router-link>
            </div>
        `
    };

    const User = {
        props: ["name"],
        template: `
            <h2> Hello, {{ name }} </h2>
        `
    }

    const router = new VueRouter({
        routes: [
            {path: '/', component: Home},

            {
                name: 'User',
                path: '/user',
                component: User,
                props: true        // 组件属性接收路由参数
            }
        ]
    });

    new Vue({
        el: "#demo",
        router: router
    });

</script>


编程式导航

$router.push:向history中加入新的记录

<div id="demo">
    <router-view> </router-view>
</div>

<script>

    const Home = {
        methods: {
            func: function() {
                this.$router.push({
                    name: "User",
                    params: {
                        name: "foo"
                    }
                });
            }
        },
        template: `
            <div>
                <h2> home page </h2>
                <button @click="func"> go to user page </button>
            </div>
        `
    }

    const User = {
        data: function() {
            return {
                name: ''
            }
        },
        created: function() {
            this.name = this.$route.params.name;
        },
        template: `
                <h2> Hello, {{ name }} </h2>
            `
    }

    const router = new VueRouter({
        routes: [
            {path: "/", component: Home},
            {name: "User", path: '/user', component: User}
        ]
    });

    new Vue({
        el: "#demo",
        router: router
    });

</script>

tips: 要注意 $router$route的区别,前一个相当于全局路由,后一个代表当前页面的路由

点击 <router-link :to="..."> 等同于调用 router.push(...)

声明式编程式
<router-link :to="...">router.push(...)

如果提供了 path,params 会被忽略

const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123

// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
  • $router.replace:跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
  • $router.go:这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步


history 模式与404错误

使用 history 模式,可以去掉 url 中的‘#’,同时保持原本hash模式的无加载跳转。

<div id="demo">
    <router-view> </router-view>
</div>

<script>
    const Home = {
        template: `
            <div>
                <h2> home page </h2>
                <router-link to="/user"> go to user </router-link>
            </div>
        `
    };
    const User = {
        template: `
            <h2> Hello </h2>
        `
    };

    const router = new VueRouter({
        mode: "history",    // 设置history 模式
        routes: [
            {path: "/", component: Home},
            {path: "/user", component: User},
        ]
    });

    new Vue({
        el: "#demo",
        router: router
    });
</script>

  1. hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

  2. history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id。如果后端缺少对 /book/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”


重定向

<div id="demo">
    <router-view> </router-view>
</div>

<script>

    const PageA = {
        template: `
            <h2> Page A </h2>
        `
    };

    const router = new VueRouter({
        routes: [
            {path: "/A", alias: "/C", component: PageA},
            {path: "/B", redirect: "/A"},
        ]
    });

    new Vue({
        el: "#demo",
        router: router
    });

</script>
  • redirect 方式会改变url
  • alias 不会不会改变url


嵌套路由

当几个页面只有部分子内容不同时,可以使用嵌套路由

<div id="demo">
    <router-view> </router-view>
</div>

<script>

    const Home = {
        template: `
            <div>
                <h2> 嵌套路由 </h2>
                <router-view> </router-view>
            </div>
        `
    };
    const PageA = {
        template: `
            <h3> A </h3>
        `
    };
    const PageB = {
        template: `
            <h3> B </h3>
        `
    };

    const router = new VueRouter({
        routes: [
            {
                path: "/",
                component: Home,
                children: [
                    {path: 'A', component: PageA},
                    {path: 'B', component: PageB},
                ]
            },
        ]
    });

    new Vue({
        el: "#demo",
        router: router
    });

</script>


命名路由视图

用于同级展示多个路由视图

用 name 属性标识 router-view, 缺省则name为default

<div id="demo">
    <router-view> </router-view>
    <router-view name="view1"> </router-view>
</div>

<script>

    const PageA = {
        template: `
            <h3> A </h3>
        `
    };
    const PageB = {
        template: `
            <h3> B </h3>
        `
    };

    const router = new VueRouter({
        routes: [
            {
                path: '/',
                components: {
                    default: PageA,
                    view1: PageB
                }
            },
        ]
    });

    new Vue({
        el: "#demo",
        router: router
    });

</script>

这种效果和直接组件复用类似:

<div id="demo">
    <va> </va>
    <vb> </vb>
</div>

<script>

    const PageA = {
        template: `
            <h3> A </h3>
        `
    };
    const PageB = {
        template: `
            <h3> B </h3>
        `
    };

    new Vue({
        el: "#demo",
        components: {
            'va': PageA,
            'vb': PageB,
        }
    });

</script>


导航守卫

  相当于一个拦截器,可以选择路由的跳转或取消。比如当用户访问一个登录限制的页面时,就可以用全局守卫判断当前用户的登录状态,如果未登录则需要跳转到登录页

全局守卫

<div id="demo">
    <router-view> </router-view>
</div>

<script>
    const Home = {
        template: `
            <div>
                <h2> home page </h2>
                <router-link to="/user"> go to user </router-link>
            </div>
        `
    };
    const User = {
        template: `
            <h2> Hello </h2>
        `
    };

    const router = new VueRouter({
        routes: [
            {path: "/", component: Home},
            {path: "/user", component: User},
        ]
    });

    router.beforeEach((to, from, next)=> {
        alert("全局守卫1");
        to.path === "/user"? next(false): next();
    });

    router.beforeEach((to, from, next)=> {
        alert("全局守卫2");
        next();
    });

    new Vue({
        el: "#demo",
        router: router
    });
</script>

在这个例子中,beforeEach全局守卫通过next函数调用下一个守卫

每个守卫方法接收三个参数:

  • to / from: 即将进出的路由
  • next: 一定要调用该方法来 resolve 这个钩子
    • next(): 进行管道中的下一个钩子
    • next(false): 中断当前的导航
    • next(‘/‘) 或者 next({ path: ‘/‘ }): 终端当前导航,跳转到一个不同的地址

全局后置钩子:和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身

router.afterEach((to, from) => {/*...*/})

路由级守卫

如果你不想全局配置守卫的话,你可以为某些路由单独配置守卫:

这些守卫与全局前置守卫的方法参数是一样的:

const router = new VueRouter({
    routes: [
        {
            path: '/foo',
            component: Foo,
            beforeEnter: (to, from, next) => { 
                // 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
                // ...
            }
        }
    ]
});

组件级守卫

这些守卫与全局前置守卫的方法参数是一样的:

  • beforeRouteEnter 进入路由前
  • beforeRouteUpdate 路由复用同一个组件时
  • beforeRouteLeave 离开当前路由时

beforeRouteEnter 守卫 不能 访问 this,因为此时即将登场的新组件还没被创建。

可以通过给这个守卫的next注册回调函数,并把组件实例传给这个回调函数。因为在整个导航流程的最后一步即DOM渲染更新之后,这些在beforeRouteEnter中注册的回调函数会被调用。

<div id="demo">
    <router-view> </router-view>
</div>

<script>
    const Home = {
        template: `
            <div>
                <h2> home page </h2>
                <router-link to="/user"> go to user </router-link>
            </div>
        `
    };
    const User = {
        data: function() {
            return {
                name: "Foo"
            }
        },
        beforeRouteEnter: function(to, from, next) {
            console.log(this);  // 此时this为window对象
            next(vm=> {vm.name = "Bar";});
        },
        template: `
                <h2> Hello, {{ name }} </h2>
            `
    };

    const router = new VueRouter({
        routes: [
            {path: "/", component: Home},
            {path: "/user", component: User},
        ]
    });

    new Vue({
        el: "#demo",
        router: router
    });
</script>

tips: 这个next回调函数的参数vm就是这个Vue组件实例

守卫导航的优先关系是:全局—>路由—>组件



参考

vue-router两种模式,到底什么情况下用hash,什么情况下用history模式呢?
Vue Router
vue 路由传参 params 与 query两种方式的区别
vue-router入门(3)—— 导航守卫的使用和流程探究
关于vue中beforeRouteEnter使用的误区

-------------本文结束-------------