美文网首页
Vue Router简明教程

Vue Router简明教程

作者: li4065 | 来源:发表于2020-05-31 15:39 被阅读0次

    官方路由插件,配合Vue使用,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们

    本教程中 vue 是 2.6+,vue-router 3.2+,webpack 4.42+

    /// index.html 要有 router-view 标签
    <div id="app">
        <router-view></router-view>
    </div>
    /// index.js
    import vue from 'vue';
    import VueRouter from 'vue-router';
    
    /*
    * 下面是异步组件的写法,使用webpack中的功能,这样是比较合理的拆分,避免页面打包的资源
    * 过大
    */
    const App = () => import(/* webpackChunkName: "app" */ '../component/main');
    const Cat = () => import(/* webpackChunkName: "cat" */ '../component/cat');
    const Dog = () => import(/* webpackChunkName: "dog" */ '../component/dog'); 
    
    vue.use(VueRouter);
    
    const routes = [
        { path: '/cat', component: Cat },
        { path: '/dog', component: Dog },
        { path: '/', component: App},
        { path: '*', redirect: '/' }
    ] // routes 表示定义的路由
    
    const router = new VueRouter({
        routes
    }) // router 是vue router的实例
    
    let vm = new vue({
        el: '#app',
        router,
        render: h => h('router-view')
    })
    

    { path: '*', redirect: '/' } 这个匹配中的 * 是通配符路由,会匹配所有路径,所以这个含有通配符的路由应该放在最后,路由 { path: '*' } 通常用于客户端 404 错误。
    { path: '/cat-*', component: Cat } 这也是另外一种通配符,会匹配 /cat- 开头的任意路径,当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分,比如/cat-black,对应值为 { pathMatch: "black" }

    匹配优先级

    如上例,routes 接收的是一个数组,那么这必然涉及到一个匹配的优先级。这里规则很简单,就是谁先定义的,谁的优先级就最高。

    动态路由匹配

    this.$route 可以访问路由器,params 可以获取路径参数

    // 路由参数中的 /:kind 就是一个路径参数
    { path: '/dog/:kind', component: Dog },
    ...
    /// dog.vue
    <template>
        <div>
            <p>dog: {{kind}}</p>
        </div>
    </template>
    <script>
    export default {
        data () {
            return {
                kind: ''
            }
        },
        mounted () {
            this.kind = this.$route.params.kind // 通过$route.params获取到指定路径参数
        }
    }
    </script>
    

    多段路径参数匹配规则如下

    模式 匹配路径 $route.params
    /dog/:kind /dog/erha { kind: "erha" }
    /dog/:kind/eat/:food /dog/erha/eat/rice {kind: "erha", food: "race"}

    beforeRouteUpdate

    当改变路径参数时,Vue会尽可能的复用组件,减少不必要渲染造成的性能损耗,但这就造成了有些生命周期函数就不会再次被调用,如果在这些生命周期函数中,设置了必须执行的代码,那这就会有问题

    /// dog.vue 对应路径参数为 /dog/:kind/eat/:food
    <p>dog: {{kind}} eat: {{eat}}</p>
    ...
    data () {
      return {
        kind: '',
        eat: ''
      }
    },
    mounted () {
      this.kind = this.$route.params.kind;
      this.eat = this.$route.params.food;
    },
    

    上述代码中 eat 这个参数时在 mounted 这个钩子函数中设置的,如果在浏览器中把路径从 /dog/erha/eat/rice 改为 /dog/erha/eat/meat ,eat 的值并不会更新,就是因为路径参数变化时,Vue 不会重新触发那些钩子函数,要解决这个问题,只需要使用 beforeRouteUpdate 函数

    /// dog.vue
     beforeRouteUpdate (to, from, next) {
       this.eat = to.params.food;
       next();
     }
    

    beforeRouteUpdate 一共有三个参数:

    参数 含义
    to 即将要进入的目标 路由对象
    from 当前导航正要离开的 路由对象
    next 后续导航的处理逻辑

    to、from 就是普通的路由对象,next 会根据其传参不同,功能也不同

    参数 含义
    () 不特殊处理,继续进入下一个导航 next()
    false 中断导航,如果URL改变,会把URL重置为from路由对应的地址 next(false)
    '/'或{path: '/'} 强制跳转到一个指定地址 next('/'); next({path: '/'})
    Error实例 触发路由实例上的 onError 事件 next(new Error())

    当监听了 beforeRouteUpdate ,内部的 next 方法是必须执行的,否则路由行为就会中断

    /// index.js
    const router = new VueRouter({
        routes
    })
    router.onError(() => {
        console.log('error')
    })
    ...
    /// dog.vue
    beforeRouteUpdate (to, from, next) {
      this.eat = to.params.food;
      next(new Error()); // 这样当路由更新时会触发绑定的 onError 事件
    }
    

    嵌套路由

    这是一种动态路由映射不同组件的行为

    /// 此时假定有一个查询指定汽车名下车类型的要求
    /// automobile.vue
    <template>
        <div>
            名称: {{ $route.params.name }}
            <router-view></router-view> // 这里是嵌套路由的入口
        </div>
    </template>
    ...
    /// car.vue
    <template>
        <div>car 的特殊参数</div>
    </template>
    ...
    /// bus.vue
    <template>
        <div>bus 的特殊参数</div>
    </template>
    ...
    /// bicycle.vue 这个是为说明嵌套路由地址的问题
    <template>
        <div>这是自行车</div>
    </template>
    ...
    /// 在入口路由做如下设置
    {
      path: '/automobile/:name', component: AutoMobile, children: [
        {
            path: 'car', component: Car
        },
        {
            path: 'bus', component: Bus
        },
        {
            path: '/bicycle', component: Bicycle
        }
      ]
    }
    

    此时如果我们访问 /automobile/byd/bus 或者 /automobile/byd/carautomobile 组件内的 router-view 就会加载相关组件。

    相关嵌套的组件的匹配规则可以在 children 中进行设置,这里要留意嵌套路由中 path 的地址,如果匹配路径不加 / ,会如预期一样拼接地址,但上例中的 '/bicycle' 就会匹配到 /bicycle 的路由。

    如果我们直接访问 /automobile/byd 此时并没有子路由可以匹配,automobile 组件内的 router-view 不会加载内容,不过如果你想设置一个默认组件,可以匹配空字符串

    path: '', component: Car

    编程式导航

    路由的跳转可以通过暴露的方法,直接进行跳转操作,一般情况下也没必要再组件内设置 <router-link :to="..."> 来设置页面的跳转,这种方式毕竟不灵活

    <template>
        <div>
            名称: {{ $route.params.name }}
            <router-view></router-view>
            <button @click="goToPage('car')">car</button>
            <button @click="goToPage('bus')">bus</button>
        </div>
    </template>
    <script>
    export default {
        methods: {
            goToPage (val) {
                this.$router.push(val)
            }
        }
    }
    </script>
    

    router** 是对应 VueRouter 的实例对象,**route 是当前路由的基本信息

    命名路由

    就是对一个路由,提供一个别名快速访问,这个别名用在控制页面跳转时作为特别参数使用

     path: '/automobile/:name', component: AutoMobile, name: 'automobile',
    

    router.push(location, onComplete?, onAbort?)

    router.push 每一次操作都会产生一次新的历史记录

    location 可以是一个字符串,也可以是一个对象

    • 当为字符串时,就是在当前路由下按规则拼接子路由
    /// 针对上面的内容,有这么个路由配置
    { path: '/automobile', component: AutoMobile},
    

    假定访问 /automobile 路由,如果通过 router.push('car'),那路由就会变成 /automobile/car ,如果为 router.push('/bus') ,那么路由就变成 /bus

    要注意,以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。(源于官网)

    • 当为对象时,这个对象可以设置四个属性,path、params、query和name
      • path 和 params 参数不共存,path 可以搭配 query 一起使用
      • params 可以和 name 一起使用
    /// 此处假定当前路由为 /automobile/byd 组件内容如本节中有两按钮,通过 goToPage 设置页面的跳转
    ...
    /// 单独使用 path
    router.push({
        path: 'car'
    }) // /automobile/car
    ...
    /// 单独使用 path 但是路径写完整可以跳到任何路径
    router.push({
        path: '/automobile/byd/car?color=red'
    })
    ...
    /// 使用 path 和 query
    router.push({
        path: 'car',
        query: {
            color: 'red'
        }
    }) // /automobile/car?color=red
    ...
    /// 使用 name 和 params,注意上一小节对路由的命名
    router.push({
      name: 'automobile',
      params: {
        name: 'byd'
      }
    }) // /automobile/:name 这种相当于直接设置了name值,最终路径为 /automobile/byd
    /// 如果定义路由时没有可以接受的参数,就会忽略 params 的值
    { path: '/list', component: List, name: 'automobile' },
    这时跳转后路径直接为 /list
    

    router.replace(location, onComplete?, onAbort?)

    功能和用法与 push 方法一直,区别在于这个并不会产生历史记录

    router.go(n)

    参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

    • router.go(1) 相向一步,router.go(-1) 后退一步
    • router.go(-100) 如果历史记录并没有这么多条,就会失败

    命名视图

    router-view 进行命名,多个命名的视图可以组成各种复杂的布局

    /// index.vue
    <template>
        <div>
            <router-view name='header'></router-view>
            <router-view name='body'></router-view>
            <router-view name='footer'></router-view>
        </div>
    </template>
    ...
    /// header.vue
    <template>
        <div>Header</div>
    </template>
    ...
    /// content.vue
    <template>
        <div>Content</div>
    </template>
    ...
    /// footer.vue
    <template>
        <div>Footer</div>
    </template>
    ...
    /// 路由设置如下
    { path: '', component: Index, children: [
      {
        path: '/index', components: { // 注意如果要使用多个组件要使用 components
          header: Header,
          body: Body,
          footer: Footer
        } // 这样就对每个命名视图,设置了载入的组件
      }
    ]},
    

    在使用命名视图时,要考虑好组件最终会渲染在哪个视图上,上面设定的 components 是基于 index 组件,在组件内是有这三个命名视图,所以页面可以正常加载组件内容

    /// 如果这样设置,在根元素,只有一个默认的视图,就不能正常渲染
    {
      path: '/index', alias: '/indexs', components: {
        header: Header,
        body: Body,
        footer: Footer
      }
    }
    

    重定向和别名

    重定向

     { path: '/indexs', redirect: '/index'},
     /// 通过 redirect 设置重定向的路径,当访问 /indexs 会直接重定向为 /index 这期间并不会产生浏览器记录
    

    别名

    path: '/index', alias: '/indexs', 
    /// alias 就是给路由设定一个别名,访问 /indexs 和 /index 是一样
    

    路由组件的传参

    /// detail.vue 
    /// 正常情况下,detail 页面会拿到 id 去请求 详情接口,拉取详情页数据,这里是简化
    <template>
        <div>
            详情页编号:{{ $route.params.id }}
        </div>
    </template>
    <script>
    export default {
    
    }
    </script>
    ...
    /// 路由设置如下
    { path: '/detail/:id', component: Detail},
    

    这里能看出一个问题,就是 detail 组件和路由联系过于紧密,因为我们是从路由页对应的参数上拿初始数据,这就造成了这个组件的通用型不高。

    针对此,可以通过路由传参数对组件进行改造

    /// 第一步 目标组件与 $route 解藕
    <template>
        <div>
            详情页编号:{{ id }}
        </div>
    </template>
    <script>
    export default {
        props: ['id'] // 设定在 props 中,这更像是一个普通组件
    }
    </script>
    ...
    /// 第二步 在路由参数中设置开启 props 传参形式
    { path: '/detail/:id', component: Detail, props: true}
    

    路由开启 props 传参形式后,需要为当前路由下的命名视图都设置 props 选项

    /// header.vue
    <template>
        <div>Header <p>welcome {{ name }}</p></div>
    </template>
    <script>
    export default {
        props: ['name']
    }
    </script>
    ...
    /// footer.vue
    <template>
        <div>Footer @ {{ $route.params.time}}</div>
    </template>
    /// footer 和 header 此处使用了两种不同方式获取参数
    ...
    /// 配置信息如下
    { path: '/detail', component: Detail, children:[
      {
        path: ':id/:name/:time', components: {
          header: Header,
          footer: Footer
        }, props: {
            header: true // 这里设置 header 使用 props 的方式获取参数
        }
      }
    ], props: true},
    /// 假定访问路径为 /detail/23/rede/2020 对应的params值为 
    {id: "23", name: "rede", time: "2020"}
    ...
    /// 启用了 props 方式,params 对应的值都可以直接获取,比如
    <p>welcome {{ name }} {{ time }}</p>
    ...
    props: ['name', 'time']
    

    HTML5 History 模式

    Vue-router 默认情况下,路由是hash模式,也就是页面路径会是 http://localhost:8181/#/detail/23/rede/2020 ,这里一直使用 webpack-dev-server 起的服务,在对应配置信息为

    devServer: {
        port: 8181
    },
    

    这种hash模式看起来不简洁,可以开启 history 的模式,做如下配置

    /// webpack.config.js
    output: {
      filename: '[name].js',
      publicPath: '/' // 记得设置查找路径,这表示在引入静态资源时,从根路径开始引入
    },
    devServer: {
      port: 8181,
      historyApiFallback: true // 开启history模式
    },
    ...
    /// index.js
    const router = new VueRouter({
        mode: 'history',
        routes
    })
    ...
    /// 这样访问路径就是 http://localhost:8181/detail/23/rede/2022
    

    webpack-dev-server 内部其实就是使用官方文档中推荐的 connect-history-api-fallback 然后通过暴露的方法进行调用

    相关钩子函数

    VueRouter的钩子函数分为三个层面

    • 全局钩子函数:beforeEach、beforeResolve、afterEach
    • 路由钩子函数:beforeEnter
    • 组件钩子函数:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
      |函数名|参数|执行时期|备注|
      |---|---|---|---|
      |beforeEach|(to, from, next)|全局||
      |beforeResolve|(to, from, next)|全局|会在路由和组件内的钩子函数执行后调用|
      |afterEach|(to, from)|全局|无需next参数||
      ||||||
      |beforeEnter|(to, from, next)|路由||
      |||||
      |beforeRouteEnter|(to, from, next)|组件|组件实例还没被创建,再其内部不可通过this获取当前组件内的数据|
      |beforeRouteUpdate|(to, from, next)|组件|在当前路由改变,但是该组件被复用时调用,此时可以调用this获取组件内数据|
      |beforeRouteLeave|(to, from, next)|组件|导航离开该组件的对应路由时调用,可以通过this获取组件内数据|

    钩子函数的执行顺序

    整个路由变化为 /list 变为 /detail/23/rede/2020 再到 /detail/23/rede/2021 的演变程度

    • hash模式
      |路由变化|钩子函数执行顺序|
      |---|---|
      |进入 /list| beforeEach -> beforeEnter -> beforeRouteEnter -> beforeResolve -> afterEach|
      |从 /list/detail/23/rede/2020|beforeRouteLeave (list组件中的钩子函数) -> beforeEach -> beforeEnter -> beforeRouteEnter -> beforeResolve -> afterEach|
      |从 /detail/23/rede/2020/detail/23/rede/2021|beforeEach -> beforeRouteUpdate -> beforeResolve -> afterEach|

    • history模式
      |路由变化|钩子函数执行顺序|
      |---|---|
      |进入 /list| beforeEach -> beforeEnter -> beforeRouteEnter -> beforeResolve -> afterEach|
      |从 /list/detail/23/rede/2020|beforeEach -> beforeEnter -> beforeRouteEnter -> beforeResolve -> afterEach|
      |从 /detail/23/rede/2020/detail/23/rede/2021|beforeEach -> beforeEach -> beforeRouteEnter -> beforeResolve -> afterEach |

    history 模式下组件内的钩子函数 beforeRouteLeave 不会执行,而且同路由切换参数时,钩子函数也是重新执行,并不是 hash 模式下的如预期的执行顺序

    分析两个过程,可以看到全局的钩子函数,都会执行,路由上的钩子函数 beforeEnterhash 模式下更新时不会执行,组件的钩子函数,进入时都会执行 beforeRouteEnterbeforeRouteUpdate 只会在 hash 模式下更新参数执行,beforeRouteLeave 只会在 hash 模式下,切换路由时执行

    路由的 meta 信息

    路由在定义时可以设置 meta 信息,在这里可以定义一些信息,供页面中使用,比较直接的用处,就是官网上提到的登陆权限设置

    { path: '/list', component: List,  meta: { requiresAuth: true } },
     { path: '/detail', component: Detail, children:[
            {
                path: ':id/:name', components: {
                    header: Header
                }, props: {
                    header: true
                },
                meta: { requiresAuth: true },children: [{
                    path: ':time',
                    components: {
                        header: Header,
                        footer: Footer
                    }
                }]
            }
        ], props: true,  meta: { requiresAuth: false }}
    ...
    /// index.js
    router.beforeEach((to, from, next) => {
        console.log(to.matched);
        next()
    })
    

    $route.matched 会返回路由的信息,这其中就包含 meta 的信息,这是个数组,返回数组数按子路由进行拆分,比如 /list 返回的数组就是一条,而如果按例子配置,访问 /detail/23/rede/2020 ,那返回的数据就是三条,对应path 分别为

    {path: "/detail" ...
    {path: "/detail/:id/:name" ...
    {path: "/detail/:id/:name/:time" ...
    

    路由上设定的 meta 信息就是在这里的对象上,通过获取这个可以控制权限或者别的什么

    过渡动效

    VueRouter 的路由切换是基于对router-view 内容的修改实现的,整体感觉很像是动态组件的感觉,可以使用过渡组件,在路由切换时添加上一些动效

    /// 下面例子,参考官网,稍改
    /// app.vue
    <div id="app">
      <ul>
        <li>
            <router-link to="/">/</router-link>
        </li>
        <li>
            <router-link to="/list">/list</router-link>
        </li>
        <li>
            <router-link to="/parent">/parent</router-link>
        </li>
        <li>
            <router-link to="/parent/foo">/parent/foo</router-link>
        </li>
        <li>
            <router-link to="/parent/bar">/parent/bar</router-link>
        </li>
      </ul>
      <transition name="fade" mode="out-in">
        <router-view class="view"></router-view>
      </transition>
    </div>
    ...
    /// list
      <div>list</div>
    ...
    /// parent.vue
    <template>
      <div class="parent">
        <h2>Parent</h2>
        <transition :name="transitionName">
          <router-view class="child-view"></router-view>
        </transition>
      </div>
    </template>
    <script>
    export default {
      data() {
        return {
          transitionName: "slide-left"
        };
      },
      beforeRouteUpdate(to, from, next) {
        const toDepth = to.path.split("/").length;
        const fromDepth = from.path.split("/").length;
        this.transitionName = toDepth < fromDepth ? "slide-right" : "slide-left";
        next();
      }
    };
    </script>
    ...
    /// index.js
      routes: [
        { path: "/", component: App },
        { path: "/list", component: List },
        {
          path: "/parent",
          component: Parent,
          children: [
            { path: "", component: Default },
            { path: "foo", component: Foo },
            { path: "bar", component: Bar }
          ]
        }
      ]
    ...
    /// 样式如下
    .fade-enter-active, .fade-leave-active {
        transition: opacity 0.5s;
    }
    .fade-enter, .fade-leave-to {
        opacity: 0;
    }
    .view {
        position: relative;
    }
    .child-view {
      position: absolute;
      width: 100%;
      transition: all 0.8s ease;
      top: 40px;
    }
    .slide-left-enter, .slide-right-leave-active {
      opacity: 0;
      transform: translate(100%, 0);
    }
    .slide-left-leave-active, .slide-right-enter {
      opacity: 0;
      transform: translate(-100% 0);
    }
    

    这里一共用到了两个过渡组件一个是在 app.vue 组件中的 transition name="fade",另一个是在 parent.vue 中的 transition :name="transitionName" ,按照动态组件的思想来思考,只有在对应包裹的 router-view 进行切换时才会触发过渡,所以添加过渡效果时要明确自己的要触发的路由是哪个

    数据获取时机

    通过之前的钩子函数以及路由参数的分析,我们如果要去请求数据有两种思路来实现:

    • 无视路由提供的钩子函数,完全依赖组件内的生命周期函数(比如 mounted),通过获取路由参数($route.params),在组件内合适的生命周期中触发获取数据的方法
    • 利用路由提供的钩子函数,在组件内调用,比如 beforeRouteEnter 的钩子函数,从中获取相关路由参数,再去获取数据

    两者从使用角度来看都没啥问题,按需使用

    相关文章

      网友评论

          本文标题:Vue Router简明教程

          本文链接:https://www.haomeiwen.com/subject/xnsfzhtx.html