说一说实战项目升级从vue2到vue3 之router

99 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

先附上vue-router 官方文档访问地址https://router.vuejs.org/zh/

@TOC

@[TOC](vue2 写法)

import Vue from 'vue'
import VueRouter from 'vue-router'
import Layout from '../layout'
import modules from './modules/require'
Vue.use(VueRouter)
const routes = [{
    redirect: '/',
    path: '/index',
    name: 'layout',
    component: Layout,
    children: [
      ...modules
    ]
  },
  {
    path: '/', // 官网
    name: 'home',
    component: () => import('@/views/home')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes,
  // scrollBehavior () {
  // 浏览器保存记录对象
  // history.pushState(null, null, document.URL)
  // }
})

const VueRouterPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(to) {
  return VueRouterPush.call(this, to).catch(err => err)
}

export default router

modules------require.js

const files = require.context('./', false, /\.js$/)
let routerArr = []

files.keys().forEach(item => {
  if (item !== './require.js') {
    routerArr = routerArr.concat(files(item).default)
  }
})
export default typeof routerArr[0] === 'undefined' ? '' : routerArr

modules文件夹里面是各个路由的js文件,js里面写的便是改路由 例如:license.js

export default [{
  path: '/license',
  name: 'license',
  icon: 'license-menu',
  meta: {
    isMenu: true,
    title: '授权信息',
    active_tab: '/index',
    keepAlive: true,
    roles: [1, 2, 3]
  },
  component: () => import('@/views/license')
}]

Layout 是封装的公共布局,包块但不限于导航栏、菜单栏、个人中心等等。

我们都知道vue-routerrouter.beforeEach这样一个全局的路由钩子,在这个方法里面可以做路由拦截个重定向,这个钩子可以在router.js里面 写,也可以在main.js里面写,看个人习惯。

// 全局路由钩子
router.beforeEach((to, from, next) => {
  const pathName = to.name
  // 判断该路由是否需要登录权限
  if (pathName)) {
   
  } else {
    next() // 确保一定要有next()被调用
    return
  }
})

@TOC router.js

import { createRouter, createWebHistory } from "vue-router";
import BasicLayout from "../layouts/basicLayout.vue";
const routes = [
  {
    path: "/",
    name: "index",
    meta: { title: "首页" },
    component: BasicLayout,
    redirect: "/home",
    children: [
      {
        path: "/home",
        name: "home",
        meta: { title: "首页", isHideMenu: false },
        component: () =>
          import("../views/home/index.vue"),
      }
    ],
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});
export default router;

BasicLayout 文件便是所封装的公共组件,因为这个是demo 所以简单处理没有进行拆分

@TOC

简单说一下 举一个例子作为说明,比如说我们常用到的菜单栏

@TOC

<template>
  <div class="sider-container">
    <!-- collapsible 开关 -->
    <a-layout-sider v-model="isCollapsed">
      <a-menu
        mode="inline"
        style="width: 224px; min-height: 100vh; padding-top: 23px"
        :inline-collapsed="isCollapsed"
        :selectedKeys="selectedKeys"
        :openKeys="openKeys"
        @openChange="changeMenu"
        @click="changeRoute"
        class="menu-box"
      >
        <template v-for="item in menuList">
          <template v-if="item.meta && item.meta.isMenu && item.meta.roles.includes(userInfo.role)">
            <!-- 有子菜单 -->
            <template v-if="item.children">
              <a-sub-menu :key="item.path">
                <span slot="title">
                  <icon v-if="item.icon" :name="item.icon" />
                  <span>{{ item.meta.title }}</span>
                </span>
                <template v-for="column in item.children">
                  <a-menu-item
                    :key="column.path"
                    v-if="column.meta && column.meta.isMenu && column.meta.roles.includes(userInfo.role)"
                  >
                    <span class="laout-level_1">
                      <i class="point">.</i>
                      <span >{{ column.meta.title }}</span>
                    </span>
                  </a-menu-item>
                </template>
              </a-sub-menu>
            </template>
            <!-- 没有子菜单 -->
            <template v-else>
              <a-menu-item :key="item.path">
                <icon v-if="item.icon" :name="item.icon" />
                <span>{{ item.meta.title }}</span>
              </a-menu-item>
            </template>
          </template>
        </template>
      </a-menu>
    </a-layout-sider>
  </div>
</template>

<script>
export default {
  props: {
    routes: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      menuOrderList: [],
      menuList: [],
      isCollapsed: false,
      firstTab: [],
      selectedKeys: [this.$route.path],
      openKeys: [],
      userInfo:{},
      expireOption:"route"
    };
  },
  mounted() {
    //想要排序就自己定个顺序,不需要就算了
    //menuOrderList 
    // 路由顺序调整
    this.routes.map(item => {
      if (this.menuOrderList.includes(item.meta.title)) {
        this.menuList[this.menuOrderList.indexOf(item.meta.title)] = item
      }
    })
    // 展开菜单
    if (this.$route.meta.openKeys) {
      this.openKeys = this.$route.meta.openKeys
    }
    // 选中菜单 有二级
    if(this.$route.meta.path){
      this.selectedKeys = [this.$route.meta.path]
    }else{
      this.selectedKeys = [this.$route.path]
    }
  },
  methods: {
    changeMenu (key) {
      this.openKeys = key
    },
    changeRoute ({key}) {
      if (this.$route.path === key) {
        return
      }
      this.openKeys = ['/' + key.split('/')[1]]
      this.$emit('chooseItem', key);
      this.$router.push({
        path: key
      })
    },
  },
  watch:{
    $route () {
      if(this.$route.meta.path){
        this.selectedKeys = [this.$route.meta.path]
      }else{
        this.selectedKeys = [this.$route.path]
      }
    },
  }
};
</script>

<style lang="less" scoped>
.laout-level_1 {
  position: relative;
  z-index: 2;
  .point {
    position: absolute;
    left: -14px;
    top: -19px;
    font-size: 30px;
  }
  .sub-title {
    padding-left: 12px;
  }
}
.svg-icon {
  margin-right: 8px;
  vertical-align: -3px !important;
}
.menu-box {
  .ant-menu-item-selected {
    .svg-icon {
      fill: @primary-color !important;
    }
  }
}
</style>

@TOC

<template>
  <a-layout>
    <a-layout-header :style="{ position: 'fixed', zIndex: 99, width: '100%' }">
      <div class="logo" />
      <a-menu
        v-model:theme="state.navTheme"
        v-model:mode="state.mode"
        v-model:selectedKeys="state.selectedKeys"
        :style="{ lineHeight: '64px' }"
        @click="changeRoute"
      >
        <div v-for="item in state.menuData" :key="item.name">
          <a-menu-item :key="item.name" v-if="!item.meta.isHideMenu">
            <span>{{ item.meta.title }}</span>
          </a-menu-item>
        </div>
      </a-menu>
    </a-layout-header>
    <a-layout-content :style="{ padding: '0px 50px', marginTop: '96px' }">
      <div
        :style="{
          background: '#fff',
          padding: '24px',
          minHeight: '780px',
          textAlign: 'left',
        }"
      >
        <router-view></router-view>
      </div>
    </a-layout-content>
    <a-layout-footer :style="{ textAlign: 'center' }">
      COPYRIGHT © 2021
    </a-layout-footer>
  </a-layout>
</template>

<script>
import { reactive, defineComponent, watch } from "vue";
import { useRouter, useRoute } from "vue-router";
import { clearMenuItem, getMenuData } from "../utils/getMenuData";
export default defineComponent({
  setup() {
    const router = useRouter();
    const route = useRoute();
    // 获取路由菜单
    const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
    // 定义参数
    const state = reactive({
      collapsed: false, // default value
      navTheme: "dark",
      mode: "horizontal",
      selectedKeys: [], //默认选择菜单
      menuData,
    });
    // 切换路由
    function changeRoute({ key }) {
      router.push({
        path: key,
      });
    }
    state.selectedKeys = [route.name];
    watch(
      () => route.name,
      (newValue, oldValue) => {
        if (newValue == "ruleResult") {
          state.selectedKeys = [];
        } else {
          state.selectedKeys = [newValue];
        }
      }
    );

    return {
      state,
      changeRoute,
    };
  },
});
</script>
<style lang="less">
#components-layout-demo-basic {
  text-align: left;
}
#components-layout-demo-basic .ant-layout-header,
#components-layout-demo-basic .ant-layout-footer {
  line-height: 1.5;
}
#components-layout-demo-basic > .ant-layout {
  margin-bottom: 48px;
}
#components-layout-demo-basic > .ant-layout:last-child {
  margin: 0;
}
#components-layout-demo-basic > .header {
  width: 100%;
  height: 80px;
  line-height: 80px;
  color: #ffffff;
}
</style>

代码中很明显的区别,vue3说白了就是治懒癌的,按需引入,想用什么直接引入就可以了。

上述代码 可以明显的看出来vue2写法和vue3写法的区别,所以总结一下:

@TOC 我们在vue2里面想要跳转路由的的时候,可以写<router-link to="home">Home</router-link>

我们也可以用this.$router.push({})

但是在vue3中引入了setup,在setup中呢this是不存在的,不信的话可以打印一下,this是undefined

所以在vue3中我们先要引入import { useRouter, useRoute } from "vue-router";

用法如下:

const router = useRouter();
router.push({})

@TOC 我们在vue2中获取当前路由的时候用 this.$route,也可以watch监听一下

watch:{
    $route () {
      
    },
  }

vue3thisundefiend,所以我们获取方式发生了改变,用法如下

const route = useRoute();
watch(
      () => route.name,
      (newValue, oldValue) => {
        if (newValue == "ruleResult") {
          state.selectedKeys = [];
        } else {
          state.selectedKeys = [newValue];
        }
      }
    );