vue-router路由之路-极简教程

发布时间 2023-03-22 19:06:49作者: 安木夕

image.png

01、什么是前端路由?

前端路由的一个大背景就是当下流行的单页应用SPA,一些主流的前端框架,如vue、react、angular都属于SPA,那什么是SPA呢?

1.1、SPA

SPA(single-page application)单页面应用,就是浏览器只加载了一个URL地址,一个页面,应用的所有功能、交互都在这个页面内进行。而实现单页面应用的基础就是ajax,通过异步请求动态的切换页面内容、实现交互,页面整体没有刷新。这避免了页面URL跳转,用户体验也不会中断,就像原生应用一样,体验比较好。越来越多的系统在使用SPA,尤其是WebApp中使用广泛。

image.png

SPA单页应用对应的就是多页应用MPA,当然两者不是非此即彼的,主要基于业务需求,是可以共存的。

区别 单页面应用(SPA) 多页面应用(MPA)
页面组成 一个主页,包含多个页面片段 多个主页面
刷新方式 局部刷新 整页刷新
url模式 hash哈希模式 、history历史模式 history历史模式
SEO搜索引擎优化 难实现,采用页面静态化方式优化 容易实现
数据传递 同一应用内,容易 通过url、cookie、localStorage等传递,复杂
渲染性能 首次加载资源多稍慢,切换快,体验良好 切换加载资源,速度慢,用户体验差
转场动画 容易实现 好像实现不了
维护成本 相对容易 相对复杂

SPA的主要表现就是更新视图而不重新请求页面,要实现前端的页面的自主路由控制,而不会刷新页面,涉及两种主流的技术:hash模式、history模式,这算是前端路由的核心原理,简单了解一下吧!

1.2、#hash路由原理

hash( /hæʃ/ )是URL地址中#号后面的内容(包括#),原本的作用是用于HTML页面内部定位的描点,描点的变化不会导致页面重新加载。HTTP请求中也不会带#,所以刷新也不影响,这是浏览器端的本地行为。

  • 页面不刷新:hash的变化不会刷新页面,只会触发浏览器定位锚点,这是hash实现前端路由的基本原理。
  • 获取 hashwindow.location.hash
  • hash 变更事件window.hashchange监听hash变化。
  • 不同的hash会进入浏览器历史记录。

http://www.xxx.cn/#/about
http://www.xxx.cn/#/pro-info-list

所以,实现过程就比较简单了!

监测hash变化:通过hashchange事件监测hash变化 。

加载资源:根据hash值匹配不同资源进行加载、切换,在Vue中切换的其实就是不同的组件。

image

?hash-简易路由示例:codepen

<div id="app2">
    <ul id="nav" v-once>
        <li v-for="item in navs" v-if="item.title"><a v-bind:href="'#/'+item.url">{{item.title}}</a></li>
    </ul>
    <div id="main">
        <keep-alive>
            <component v-bind:is="currentComponent"></component>
        </keep-alive>
    </div>
</div>
<script>
    //components
    const NotFound = { template: '<p>404!Page not found</p>' };
    const Home = { template: '<p>首页<br>Home page</p>' };
    const Product = { template: '<p>产品页面<br>Product page<br><input></p>' };
    const About = { template: '<p>关于我们<br>About page</p>' };
    //导航路由数据
    function Route(title, url, name, component) {
        this.title = title; this.url = url; this.component = component; this.name = name;
    }
    let routes = [
        new Route("首页", "home", 'home', Home), new Route("商品", "protect", 'protect', Product),
        new Route("招聘", "hr", null, null), new Route("关于", "about", 'about', About),
        new Route(null, "not-found", 'not-found', NotFound)];
    let components = {};
    routes.forEach(item => { components[item.name] = item.component });
    //app
    let app2 = new Vue({
        el: "#app2",
        data: { currentRoute: window.location.hash, navs: Object.freeze(routes) },
        computed: {
            currentComponent: function () {
                const com = this.navs.filter(item => '#/' + item.url === this.currentRoute)[0];
                if (com && com.component) {
                    document.title = com.title;
                    return com.name;
                }
                return 'not-found';
            }
        },
        components: components,
        created: function () {
            window.addEventListener("hashchange", () => {
                this.currentRoute = window.location.hash;
            });
        }
    });
</script>

image.png

1.3、history路由原理

history 是历史对象,存放当前文档页面(或框架)的会话历史记录(不是浏览器的所有历史记录)。

history 属性/方法 描述
length 会话历史列表的记录数量
state 表示历史堆栈顶部记录的状态值,可以是任意可序列化JavaScript对象,限制为2MB
pushState(stateObj, title[, url]) 向当前会话的历史堆栈中添加一条记录
replaceState(stateObj, title[, url]) 修改 history 对象的当前(栈顶)记录
back() 返回到(历史列表中)上一个URL地址。
forward() 前进,加载(历史列表中)下一个URL地址
go(number) 加载指定相对当前网页索引位置的历史列表URL地址,go(-1)等同于back()

pushStatereplaceState 是HTML5在history上新增的API,用来新增、修改当前文档的历史记录,这两个API就是用来实现SPA单页应用前端路由的关键。他们的参数相同:(stateObj, title[, url])

  • state:一个关联历史会话记录的状态对象,主要作用是在触发 popstate事件时作为参数传递,不需要可以为null,通过history.state可以获取到当前会话的state
  • title:新页面的标题,大部分浏览器都没有管他,可以空着。
  • url:网址,可以相对、绝对地址,但不可跨域。这个url会更新到浏览器地址栏,但并不会加载该url地址,也不检查是否存在,页面也不会刷新!对,要的就是你不刷新。

基于这两个API的特性来实现前端路由。用 pushState还是 replaceState呢?两者作用一样的,唯一的不同就是pushState会产生历史记录,可用于前进、后退。

监测url地址变化

  • popstate 事件:当state变化时触发该事件,在事件中获取当前url地址。pushState、replaceState并不会触发popstate事件,前进、后退、跳转才会触发。
  • 点击事件:绑定导航按钮的click事件,pushState()更新url

加载资源:根据url值匹配不同资源进行加载、切换。

?注意,页面第一次加载的时候,不会触发popstate事件。

history-简易路由示例:codepen

<div id="app3">
    <ul id="nav" v-once>
        <li v-for="item in navs" v-if="item.title">
            <a href="#" v-on:click.prevent="navClick(item)">{{item.title}}</a></li>
    </ul>
    <div id="main">
        <keep-alive>
            <component v-bind:is="currentComponent"></component>
        </keep-alive>
    </div>
</div>
<script>
    //components
    const NotFound = { template: '<p>404!Page not found</p>' };
    const Home = { template: '<p>首页<br>Home page</p>' };
    const Product = { template: '<p>产品页面<br>Product page<br><input></p>' };
    const About = { template: '<p>关于我们<br>About page</p>' };
    //导航路由数据
    function Route(title, url, name, component) {
        this.title = title; this.url = url; this.component = component; this.name = name;
    }
    let routes = [
        new Route("首页", "home", 'home', Home), new Route("商品", "protect", 'protect', Product),
        new Route("招聘", "hr", null, null), new Route("关于", "about", 'about', About),
        new Route(null, "not-found", 'not-found', NotFound)];
    let components = {};
    routes.forEach(item => { components[item.name] = item.component });

    //拦截history.pushState,触发一个事件。不拦截换其他方式也可以,比如点击事件里。
    history.pushState = (function (type) {
        let origin = history[type];  //用闭包来存储原来的方法
        return function () {
            let out = origin.apply(this, arguments);
            let event = new Event(type);   //触发一个自定义事件pushState
            event.arguments = arguments;
            window.dispatchEvent(event);
            return out;
        }
    })('pushState');
    //app
    let app3 = new Vue({
        el: "#app3",
        data: { currentRoute: history.state?.url, navs: Object.freeze(routes) },
        computed: {
            currentComponent: function () {
                const com = this.navs.filter(item => item.url === this.currentRoute)[0];
                if (com && com.component) {
                    document.title = com.title;
                    return com.name;
                }
                return 'not-found';
            }
        },
        components: components,
        methods: {
            navClick: function (route) {
                history.pushState({ url: route.url }, null, route.url);
            }
        },
        created: function () {
            window.addEventListener("popstate", () => {
                this.currentRoute = history.state?.url;  //也可以用location.pathname获取前端url
            });
            window.addEventListener("pushState", () => { //监听自定义事件pushState
                this.currentRoute = history.state?.url;
            });
        }
    })
</script>

? 刷新页面时会重新加载当前(本地路由的)url地址,可能就404了,这就需要服务端支持,修改下nginx代理也是可以解决的。
history与hash的主要区别,就是不会出现一个#,看上去更加美观?好像也没啥区别吧!


02、开始vue-router

2.1、简介

Vue Router是Vue官方推出的路由组件,与Vue深度集成,支持hashhistory两种模式。

2.2、安装使用

  • 通过<script>标签直接引用vue-router.js
<script src="https://unpkg.com/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3/dist/vue-router.js"></script>
// 注册插件
Vue.use(VueRouter);
  • 通过 vue-cli 脚手架搭建vue的开发框架,集成了vue-router组件。
  • 注册插件:Vue.use(VueRouter)

03、vue-router3入门

3.1、Router选项

✔️Router选项 描述
routes 路由记录配置信息,Array<RouteConfig>
mode 路由模式,默认hash,选项:hashhistory、abstract(NodeJS环境)
base url的基本路径,"/app/",只有history模式有效?
linkActiveClass <route-link>激活的class名称,默认值为router-link-active
linkExactActiveClass 精确匹配激活的class,默认值为router-link-exact-active ( exact /ɪɡˈzækt/ 精确)
scrollBehavior 路由切换完成后的滚动行为回调,函数 Func(to, from, savedPosition)
parseQuery/stringifyQuery 自定义查询字符串的解析/反解析函数
//创建路由器
let vrouter = new VueRouter({ routes: vroutes, mode: 'hash', base: '/vsystem/' });
✔️routes.routeRouteConfig 初始化时配置用的路由记录RouteConfig ,在后续代码中使用的为路由对象
path 路由url路径 path: '/user'
component Component 组件,可用函数方式import懒加载组件,提高初始化的性能
components 命名视图组件,当有多个命名视图<router-view>时,也要配置对应的组件
name 给路由取个名字,自己用,没其他用途,可作为显示的中文标题
redirect 重定向路由,重定向到另外的path、route。如果带有query会解析路由出错?
alias path的别名,可一个或多个(数组Array<string>)别名,渲染组件一样
parent 父级路由,根级的parentundefined
children 子路由Array<RouteConfig>,组件内用<router-view>组件作为嵌套组件的容器
props 用于给Vue组件参数Props传值:boolean | Object | Function
- true:自动传递动态路径参route.params
- 对象,函数:把它们的结果赋值给组件props参数(按key)
beforeEnter(to, from, next) 执行路由前的一个钩子,私有的钩子,目的同全局的守卫钩子beforeEach
meta 路由元信息,自定义的个性化配置,在路由钩子中可以访问处理。meta:{title:'注册'}

✔️运行态的 $route路由对象 组件内this.$route访问,钩子函数、导航函数中的to、from、location都是此路由对象
path 路由url路径
fullPath 解析后的完整url,包含query
params 存放动态路径参数,{key:value }对象,组件内使用this.$route.params.id
query url查询参数,{key:value }对象
hash 当前路由的哈希hash
name 路由名称
meta 元数据记录
matched 匹配到的路由记录列表
interface RouteConfig = {
    path: string,
    component?: Component,
    name?: string, // 命名路由
    components?: { [name: string]: Component }, // 命名视图组件
    redirect?: string | Location | Function,
    props?: boolean | Object | Function,
    alias?: string | Array<string>,
    children?: Array<RouteConfig>, // 嵌套路由
    beforeEnter?: (to: Route, from: Route, next: Function) => void,
    meta?: any,
    // 2.6.0+
    caseSensitive?: boolean, // 匹配规则是否大小写敏感?(默认值:false)
    pathToRegexpOptions?: Object // 编译正则的选项
}
//$route路由对象
{
    name: "user-box", // 路由名称
    fullPath: "/user/21/vip?key=admin",
    hash: "", // 当前路由的哈希
    matched: [{… }],
    meta: {},
    params: { id: '21', type: 'vip' },
    path: "/user/21/vip",
    query: { key: 'admin' }
}

?简单的示例:

<style>
    .router-link-active{ background-color: rgb(168, 240, 140); }
    .nav-item{ margin: 0 10px; }
</style>
<div id="app">
    <router-link v-for="r in this.$router.options.routes" :to="r.path" class="nav-item">{{r.name}}</router-link>
    <router-view></router-view> <!-- 显示路由组件视图的容器,其实是就是一个动态组件 -->
</div>
<script>
    //路由配置RouteConfig
    let vroutes = [
        { path: '/user', name: '用户管理', component: { template: '<div>user component</div>' } },
        { path: '/login', name: '登录', component: { template: '<div>login component</div>' } }];
    //创建路由器
    let vrouter = new VueRouter({ routes: vroutes, mode: 'hash', base: '/vsystem/' });
    //app
    let app = new Vue({
        el:"#app",
        router: vrouter,
    })
</script>

image.png

3.2、router实例-创建Router()

✔️router实例-属性 描述
app、apps Vue根实例,所有apps实例
options 参数选项
currentRoute 当前激活的路由信息对象
mode 路由模式:"hash" | "history" | "abstract"
START_LOCATION 初始导航的路由地址,route对象
✔️Router实例-方法 描述
全局的导航守卫 beforeEach、beforeResolve、afterEach
编程式导航 push(route)、replace(route)、go(index)、back()、forward()
resolve() ❓解析目标位置
addRoute(parent?, RouteConfig) 添加路由记录、子路由,还有批量添加的addRoutes(routes)
getRoutes() 获取所有活跃的路由记录列表 Array<RouteConfig>
onReady(callback,errorback) 完成初始化后调用,初始化错误则调用errorback
onError(callback) 路由过程中出错时触发,算是一个全局路由异常捕获
  1. 注册插件:Vue.use(VueRouter)
  2. 创建全局共享的router路由器实例,并配置路由记录。
  3. 注入router,在根Vue组件上注入router实例,然后所有地方都可以用 this.$router访问了.
  4. <router-link>显示路由导航,<router-view>显示组件视图。
  5. 愉快的使用了,在Vue组件中访问路由的几种途径:
    • this.$router,Vue中任意地方可以访问的路由器。
    • this.$route,组件所属的route路由对象。

?创建一个路由:

<style>
    #app4 a { margin: 0 5px; }
    .router-link-active { background-color: blueviolet; color: #FFF; }
</style>
<div id="app4">
    <p>{{$router.mode}}-->{{$router.currentRoute.name}}</p>
    <div>
        <router-link to="/user/001">用户001</router-link>
        <router-link to="/login">登录</router-link>
        <a href="#" @click.prevent="$router.push('/user/002')">a-用户0002</a>
    </div>
    <hr>
    <router-view></router-view>
</div>
<script>
    // 注册插件
    Vue.use(VueRouter);
    let isAuthenticated = true;
    //组件
    const UserBox = { Prop: ['userId'], template: '<p>用户信息:{{$route.params}}</p>' };
    const Login = { template: '<p>用户登录:<br>用户名:<input></p>' };
    //路由配置
    let vroutes = [
        { path: '/user/:userId', name: '用户管理', component: UserBox },
        { path: '/login', name: '登录', component: Login, meta: { type: 'vip' } },
        { path: '/*', redirect: '/login' }];
    //创建路由器
    let vrouter = new VueRouter({ routes: vroutes, mode: 'hash', base: '/vsystem/' });
    //路由器的钩子:做一个登录权限验证,并更新文档标题
    vrouter.beforeEach((to, from, next) => {
        if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' });
        else next();
        document.title = to.name;
    });
    //app
    let app4 = new Vue({
        el: "#app4",
        router: vrouter,   //注入路由器,内部通过 this.$router 访问
    })
</script>

1.gif

3.3、path路径:string

path为路由的地址,当浏览器url地址与path匹配时,就会激活当前route路由对象,并显示器对应组件component/components

let u1 = { path: '/home', component: Home };
let u2 = { path: '/about', component: About };
let u3 = { path: '/user/register', component: Register };
let u3 = { path: '/*', component: NotFound404 };
//动态路径
let u1 = { path: '/user/:id/:type', component: UserBox }
//匹配的路径
<router-link to="/user/1/vip">用户1</router-link>

?: 动态路径参数path中可以设置动态参数,冒号:开头,后面的为参数,支持多个顺序组装:path:'/path/:参数1/:参数2'。这里的参数有什么用呢?

  • 参数都会被放到到路由对象$route.params中。
  • 组件内部直接使用:$route.params.id
  • 通过参数专递,设置路由记录props:true,参数值$route.params会传递给组件的参数Props

?*通配符*通配符匹配任意字符,可放到最后面匹配404,或重定向到默认路由。v4版本里删了,改用正则。

?优先级:如果相同的path,匹配哪个呢?按照代码的顺序,先到先得。

3.4、router-link/router-view

  • <router-link>路由导航组件,绑定路由配置,执行路由跳转。<router-link>也是一个组件,实际是一个<a>元素。
  • <router-view>路由视图组件,用来显示渲染匹配的视图组件,内部是一个Vue动态组件。如果需要动画和缓存,可以外面嵌套<transition><keep-alive>使用。
✔️<router-link> 描述
to path,路由的目标地址,字符串、路由对象。
replace 默认false=push,执行导航是用replace,还是push,对应history的两个Api
append❓ 是否添加基路径base,默认false
tag 最终渲染的的标签,默认av4中删掉了,用v-slot实现自定义
active-class 激活的类class名
exact 是否精确匹配连接地址,默认false。就是说默认是模糊匹配连接地址的,只要包含就激活了
event 触发路由的事件类型,默认click。不怎么常用,v4版本中删掉了
v-slot 作用域插槽,用来接收暴露出来的数据,<router-link>支持插槽
✔️<router-view>
name 命名视图,当有多个就需要名字了,如切换框架布局。在路由记录components中配置映射关系
<style>
    #app a{ margin: 0 10px; }
    .router-link-active { background-color: rgb(168, 240, 140); }
    /* 动画css */
    .v-enter, .v-leave-to { opacity: 0; }
    .v-enter { transform: translateX(30px); }
    .v-enter-active, v-leave-active { transition: all 1s; }
</style>
<div id="app">
    <div>
        <h4>router-link</h4>
        <router-link to='/user/1/vip'>用户管理1</router-link>
        <router-link to='/login'>登录</router-link>
        <!-- url变了,但没有触发路由 -->
        <a href="#user/2/vvip">a-用户2</a> 
        <a href="#" @click.prevent="$router.push('/user/003/vvip')">a-用户3</a>
    </div>
    <div>
        <h4>v-for绑定</h4>
        <router-link v-for="r in this.$router.options.routes" :to="r.path">{{r.name}}</router-link>
    </div><hr>
    <transition>
        <keep-alive>
            <router-view style="margin:10px"></router-view>
        </keep-alive>
    </transition>
</div>
<script>
    //路由配置RouteConfig
    let vroutes = [
        { path: '/user/:id/:type', name: '用户管理', component: { template: '<div>user component{{$route.params}}</div>' } },
        { path: '/login', name: '登录', component: { template: '<div>login component<br><input></div>' } }];
    //创建路由器
    let vrouter = new VueRouter({ routes: vroutes, mode: 'hash', base: '/vsystem/' });
    //app
    let app = new Vue({
        el: "#app",
        router: vrouter,
    })
</script>

1.gif

? 当<router-link>需要监听原生事件时,要加上原生修饰符@click.native="nav_click"

3.5、编程式导航

除了使用申明式导航<router-link>组件,也可也使用编程式的导航方法自定义实现导航,就是调用router提供的导航方法。

router实例-导航方法
push(location, onComplete?, onAbort?) location可以是url字符,也可以是route对象
replace(location, onComplete?, onAbort?) 同上,不会添加history记录,
go(index)、back()、forward() 和浏览器的history操作一样的,历史页面里跳转

  • 参数location(route对象)如果使用了path,则会忽略params (param /ˈpærəm/ 参数)。
  • 回调 onComplete?、onAbort?,在 3.1.0+,push、replace支持了Promise,会返回一个Promise对象,可链式调用了:

this.$router.push('/user').then(onComplete).catch(onAbort)

<div>
  <h4>a标签,编程式导航</h4>
  <a href="#" @click.prevent="$router.push({path:'user/21/vip',query:{key:'admin'}})">用户1</a>
  <a href="#" @click.prevent="navClick">click登录</a>
  <a @.prevent href="#/login?key=hello">原生a</a>
  <br>
  <a href="#" @click.prevent="$router.back()">后退</a>
  <a href="#" @click.prevent="$router.forward()">前进</a>
</div>
<script>
  let app = new Vue({
    el: "#app",
    router: router,
    methods: {
      navClick() {
        if (this.$router.currentRoute.path == '/login')
          return;
        this.$router.push('/login', null, () => { });  //提供一个空的onAbort
        this.$router.replace('/login');
        this.$router.push('/user').then(onComplete).catch(onAbort); //promise方式使用
        //设置了path,params的设置就忽略了
        this.$router.push({ path: '/login', query: { key: 'admin' }, params: { id: 100 } });
        //也可以用name进行导航。注意后面的catch,因为push、replace都是用promise执行的
        this.$router.push({ name: '登录', query: { key: 'admin' }, params: { id: 12 } }).catch(s => { });
      }
    }
  })
</script>

⚠️ 这里遇到一个小问题,就是通过编程事件导航的<a>链接重复点击报错:NavigationDuplicated

image.png

原来是vue-router的一个问题,3.*版本中引入了promise时也引入了这个bug,如果路由没变化(重复)就会抛出一个异常的promisev4.*版本都出来了,这个bug还没修复!<router-link>正常,只有导航编程才会。

image.png

?解决方法

  1. 判断一下当前路由是否已存在。
  2. 提供一个空的onAbort回调,或者promise的方式捕获异常。
  3. 改造一下VueRouter.prototypepush方法。

3.6、导航守卫-钩子

在导航过程中提供多种守卫(钩子函数),需要注意的是,动态path参数、<keep-alive>都会复用组件,此时组件的生命周期就不完整了,需要根据实际情况选择合适的地方。

✔️router实例-全局钩子守卫 描述
beforeEach(to, from, next) 导航执行前,可通过next取消。可用来验证登陆权限,如果没认证则跳转到登陆
beforeResolve(to, from, next) beforeEach执行后,也是前置守卫
afterEach(to, from) 导航已经离开时触发,这里没有next(不可取消路由),因为已经离开了
✔️路由配置记录route-的独有钩子
beforeEnter(to, from, next) 执行路由前调用
✔️Vue组件中新增的-钩子守卫 Vue组件的钩子
beforeRouteEnter(to, from, next) 进入前:组件路由被confirmed(已确认)前,组件还没创建,不能获取this
beforeRouteUpdate(to, from, next) 只有动态path参数复用组件时才触发,更新当前路由。
beforeRouteLeave(to, from, next) 路由将要离开该组件前触发,this可用,next(false)可取消。

?钩子的参数(to, from, next)

  • to: Route:目标路由对象。
  • from: Route:当前导航路由对象,也是要离开的。
  • next: Function:本次路由怎么执行?内置的回调,必须调用。
    • nex()/next(true):允许执行,并继续,全部钩子执行完毕,导航状态为confirmed(已确认)。
    • next(false):?不执行,中断当前导航,重置导航到from。
    • next({route}):❗中断当前导航,并进行一个新的导航到route
    • 特殊next(callback)beforeRouteEnternext接收一个回调函数,参数为组件vm,可用来请求一些ajax数据,回调会在组件创建后调用。

?导航守卫-钩子的生命周期流程图,守卫钩子测试代码: CodePen

image

?使用意见

  • 如果只是对路由做校验或逻辑处理,建议用路由的全局钩子守卫beforeEach,若只是针对某个特定路由,则用路由记录的独有钩子beforeEnter
  • 如果是需要基于组件做一些操作,如数据加载、未保存提示,则用Vue组件的路由守卫钩子。
  • 在复用组件时,通过watch监测路由对象$route的变化也是一个途径。

04、vue-router4 区别

  • 函数创建createRouter({ }),没有之前的类创建了。
  • mode路由模式:mode没了,变成了函数创建historycreateWebHistory()createWebHashHistory()
const router = createRouter({
    history:createWebHashHistory() / createWebHashHistory(),
    routes: [],
})
  • base放到了上面的创建函数参数里。
  • 实例函数router.onReady() 改为 IsReady(),该方法返回一个Promise
  • <router-view> 支持了插槽v-slot,支持就算了,关键是影响有点大。
    • <keep-alive><transition> 只能通过插槽嵌入到<router-view>里面,不像之前是放到外面的。
    • <router-view> 组件的模板也只能通过v-slot + 动态组件来实现了。
<router-view v-slot="{ Component }">
    <transition>
        <keep-alive>
            <component :is="Component" />
        </keep-alive>
    </transition>
</router-view>
  • <router-link>tag 没了,通过插槽实现。
  • 所有的导航现在都是异步的。

05、其他问题

❓如何构建多级菜单的导航?基本思路:

  • 首先是路由菜单数据,应该是后台数据库统一管理,包括菜单名称、编码、图标、路径path、上下级结构信息等等。
  • 菜单是多级的,这由功能架构来决定,路由还是一级的,因为视图区域是一致的。so,从上述数据中构建2份数据,一份实现多级Dom菜单,另外一份构建路由配置数据。

image.png

❓多标签怎么实现,可以管理使用多个标签?基本思路:

  • 首先要记录打开的路由信息,可以通过路由守卫拦截监测。
  • 用一个标签栏来显示这些打开的路由信息,自己实现切换路由即可。
  • 菜单、标签相互联动,标签删除时需按照一定规则路由到下一个标签上。
  • 刷新保存视图状态:vuex存储菜单、标签显示状态。

image.png


©️版权申明:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处!原文编辑地址-语雀