重学vue(2, 3)及其生态+TypeScript 之 vue-router4.x

认识前端路由

路由的概念在软件工程中出现,最早是在后端路由中实现的,原因是web的发展主要经历了这样一些阶段:

  • 后端路由阶段。

  • 前后端分离阶段。

  • 单页面富应用(SPA)。 下面我们就来介绍一下这些阶段

后端路由阶段

早期的网站开发整个HTML页面是由服务器来渲染的。服务器直接生产渲染好对应的HTML页面, 返回给客户端进行展示。

但是, 一个网站, 这么多页面服务器如何处理呢?

每个页面都有对应的一个网址(url),用户在浏览器中输入对应的url,服务器将返回对应的完整页面。这种情况下渲染好的页面, 不需要单独加载任何的js和css, 可以直接交给浏览器展示, 这样也有利于SEO的优化。

后端路由的缺点:

  • 一种情况是整个页面的模块由后端人员来编写和维护的。

  • 另一种情况是前端开发人员如果要开发页面, 需要通过PHP和Java等语言来编写页面代码。

  • 而且通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常糟糕的事情。

前后端分离阶段

这个阶段就是后端人员写数据接口,前端人员写页面。拿到数据接口渲染对应的页面。

前端渲染的理解:

每次请求涉及到的静态资源都会从静态资源服务器获取,这些资源包括HTML+CSS+JS,然后在前端对这些请求回来的资源进行渲染。需要注意的是,客户端的每一次请求,都会从静态资源服务器请求文件。同时可以看到,和之前的后端路由不同,这时后端只是负责提供API了。

前后端分离阶段:

随着Ajax的出现, 有了前后端分离的开发模式。后端只提供API来返回数据,前端通过Ajax获取数据,并且可以通过JavaScript将数据渲染到页面中。这样做最大的优点就是前后端责任的清晰,后端专注于数据上,前端专注于交互和可视化上。并且当移动端(iOS/Android)出现后,后端不需要进行任何处理,依然使用之前的一套API即可。目前比较少的网站采用这种模式开发(jQuery开发模式)。

SPA(single page application)单页面应用阶段

一个应用有多个页面,需要切换展示时,我们前端自己维护一个路由映射表。我们通过改变hash或者通过HTML5提供的history模式。这两种模式修改url。不会向服务器请求资源。就可以映射到不同的页面了。

改变hash,实现前端路由

通过hashchange监听hash的变化(location.href来改变),来改变占位内容。

了解更多location的内容,请访问mdn:developer.mozilla.org/zh-CN/docs/…

      <div id="app">
        <a href="#/home">home</a>
        <a href="#/about">about</a>

        <div id="content">Default</div>
      </div>

      <script>
        const contentEl = document.getElementById("content")
        window.addEventListener("hashchange", () => {
          switch(location.hash) {
            case "#/home":
              contentEl.innerHTML = "Home";
              break;
            case "#/about":
              contentEl.innerHTML = "About";
              break;
            default:
              contentEl.innerHTML = "Default";
          }
        })
      </script>

hash的优势就是兼容性更好,在老版IE中都可以运行,但是缺陷是有一个#,显得不像一个真实的路径。

history模式实现前端路由

history接口是HTML5新增的, 它有六种模式改变URL而不刷新页面:

  • replaceState:替换原来的路径。

  • pushState:使用新的路径。

  • popState:路径的回退。

  • go:向前或向后改变路径。

  • forward:向前改变路径。

  • back:向后改变路径。 下面就来简单实现以下

      <a href="/zh">zh</a>
      <a href="/llm">llm</a>
      <div id="container">我是默认页面</div>
      <script>
        const container = document.getElementById("container");
        const aEls = document.getElementsByTagName("a");
        const changeContent = () => {
          switch (location.pathname) {
            case "/zh":
              container.innerHTML = "我是zh页面"
              break;

            case "/llm":
              container.innerHTML = "我是llm页面"
              break;

            default:
              container.innerHTML = "我是其他页面"
              break;
          }
        }
        for (let a of aEls) {
          a.addEventListener("click", (e) => {
            e.preventDefault();
            const href = a.getAttribute("href");
            
            history.pushState({}, "", href)
            changeContent()
          })
        }

        
        addEventListener("popstate", changeContent)

 

vue-router

通过上面的介绍,我们现在来介绍一下vue-router吧。

Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得非常容易。目前Vue路由最新的版本是4.x版本。

vue-router是基于路由和组件的。路由用于设定访问路径, 将路径和组件映射起来。在vue-router的单页面应用中, 页面的路径的改变就是组件的切换。

安装Vue Router:npm install vue-router@4

路由的基本使用步骤

  • 创建路由组件的组件。

  • 配置路由映射: 组件和路径映射关系的routes数组。

  • 通过createRouter创建路由对象,并且传入routes和history模式。

  • 使用路由通过<router-link>(切换到指定的组件) 和<router-view>(切换路径,他就自动匹配到对应的组件)。

  • 并将router对象在app.use中注册。 下面我们来介绍一下routes(路由组件映射表)

路由的映射,只会匹配到第一个准确的路径,当匹配到多个路径时,只会展示第一个匹配到的路径的组件

  • path: 配置路由路径

  • redirect: 重定向。参数重定向的路由路径。

      {
        path: "/",
        redirect: "/home"
      }

  • children: 配置子路由。一个数组。每个数组元素都是一个route。

  • alias: 配置路由别名。string | string[]。可以简化牵头路由的路径。

     {
        path: "/home",
        name: "home",
        component: () => import("../pages/Home.vue"),
        children: [
          {
            path: "message",
            component: () => import("../pages/HomeMessage.vue"),
            
            alias: ['/zh', 'zh']
          }
        }
     }

  • name: 该路由匹配的名称。(唯一)

  • beforeEnter: 当匹配到该路由时,调用的钩子。在进入特定于此记录的守卫之前。注意如果记录有重定向属性,则 beforeEnter 无效。

  • props: 传入一个Boolean, 用来给由router-view渲染的组件传递动态路由值。以至于不需要使用$route.params.参数

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}
const routes = [{ path: '/user/:id', component: User }]

替换成

const User = {
  props: ['id'],
  template: '<div>User {{ id }}</div>'
}
const routes = [{ path: '/user/:id', component: User, props: true }]

  • meta: 在记录上附加自定义数据。

    const routes = [
        { 
            path: '/user/:id', 
            component: User, 
            meta: {name: 'zh'} 
        }
    ]

一个简单的路由映射

    

    import { createRouter, createWebHistory } from "vue-router"


    import About from '../components/About.vue'

    const Home = () => import("../components/Home.vue")

    const routes = [
      { path: "/", redirect: "/home" },
      {
        path: "/home",
        name: "home",
        component: Home
      },
      {
        path: "/about",
        name: "about",
        component: About
      }
    ]

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

两种路由模式

  • hash模式

    {
        history: createWebHashHistory()
    }

  • history模式

    {
        history: createWebHistory()
    }

内置组件

router-link: 切换到指定的组件。

下面介绍一下他的常用属性

  • to属性:是一个字符串,或者是一个对象

    <!-- 字符串 -->
    <router-link to="/home">Home</router-link>
    <!-- 渲染结果 -->
    <a href="/home">Home</a>

    <!-- 使用 v-bind 的 JS 表达式 -->
    <router-link :to="'/home'">Home</router-link>

    <!-- 同上 -->
    <router-link :to="{ path: '/home' }">Home</router-link>

    <!-- 命名的路由 -->
    <router-link :to="{ name: 'user', params: { userId: '123' }}">User</router-link>

    <!-- 带查询参数,下面的结果为 `/register?plan=private` -->
    <router-link :to="{ path: '/register', query: { plan: 'private' }}">
      Register
    </router-link>

  • replace属性:设置 replace 属性的话,当点击时,会调用 router.replace(),而不是 router.push()。

  • active-class属性:设置激活a元素后应用的class,默认是router-link-active。如果父路径相同就会添加给a元素这个class。

  • exact-active-class属性:链接精准激活时,应用于渲染的 <a> 的 class,默认是router-link-exact-active。只有父子路径都相同时,才会添加这个class。

  • custom属性: 如果设置为ture, 他表示将当前router-link包裹的元素外的a标签去除。在使用 v-slot 创建自定义 RouterLink 时很有用。我们可以自定义跳转逻辑

  • v-slot指令: 接收作用域插槽传递的props。用于自定义router-link。 在vue-router3.x的时候,router-link有一个tag属性,可以决定router-link到底渲染成什么元素。但是在vue-router4.x开始,该属性被移除了。而给我们提供了更加具有灵活性的v-slot的方式来定制渲染的内容。

v-slot如何使用呢?

  • 首先,在router-link上设置custom属性,表示我们整个元素要自定义。如果不写,那么自定义的内容会被包裹在一个 a 元素中。

  • 其次,我们使用v-slot来作用域插槽来获取内部传给我们的值:
    • href:解析后的 URL。

    • route:解析后的规范化的route对象。

    • navigate:触发导航的函数。相当于router-link中的to属性做的事情。跳转页面到指定路径。

    • isActive:是否匹配的状态。相当于router-link中的active-class属性,如果匹配到了自动添加router-link-activeclass属性。我们可以通过isActive的值来自定义active-class值。

    • isExactActive:是否是精准匹配的状态。相当于router-link中的exact-active-class属性,如果匹配到了自动添加router-link-exact-activeclass属性。我们可以通过isExactActive的值来自定义exact-active-class值。

<router-link 
    to="/home"
    v-slot="props"
    custom
>
  <button @click="props.navigate">{{props.href}} 跳转到home</button>
  <button @click="props.navigate">跳转到home, 可以将props.navigate添加到多个元素上</button>
  <span :class="{'active': props.isActive}">{{props.isActive}}</span>
  <span :class="{'active': props.isActive}">{{props.isExactActive}}</span>
  <!-- <p>{{props.route}}</p> -->
 </router-link>

router-view: 切换路径,他就自动匹配到对应的组件。
  • name: 当我们需要多个视图,而不是嵌套展示。我们就可以通过name属性来匹配到route中提供的components组件。

  • v-slot指令: 接收作用域插槽传递的props。可以用于 和 组件来包裹你的路由组件。

其中提供两个props:

  • Component:要渲染的组件。即当前路由匹配到的组件。

    <router-view v-slot="{ Component, route }">
      <transition>
        <keep-alive>
          <component
            :is="Component"
          />
        </keep-alive>
      </transition>
    </router-view>

  • route:解析出的标准化路由对象。即当前的路由对象。

路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。也可以提高首屏的渲染效率。

其实路由懒加载就是给component属性传入一个工厂函数,并且返回一个promise对象。

    component: () => import("../pages/home.vue")

动态路由

记得当时学习vue2的时候,了解到动态路由一头雾水。其实我们可以把它理解为为url传递参数,并在页面中获取到该参数,做一些事情。

很多时候我们需要将给定匹配模式的路由映射到同一个组件:例如,我们可能有一个 User 组件,它应该对所有用户进行渲染,但是用户的ID是不同的。在Vue Router中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数。

那么在User中如何获取到对应的值呢?

  • 在template中,直接通过 $route.params获取值。

  • 在created中,通过 this.$route.params获取值。

  • 在setup中,我们要使用 vue-router库给我们提供的一个hook useRoute。该Hook会返回一个Route对象,对象中保存着当前路由相关的值。

    我们也可以在同一个路由中设置有多个 _路径参数_,它们会映射到 $route.params 上的相应字段。$route.params上保存的就是动态参数对象。

错误页面处理

当我们访问没有路由映射的页面时,我们需要给用户一些提示。

对于哪些没有匹配到的路由,我们通常会匹配到固定的某个页面。比如NotFound的错误页面中,这个时候我们可编写一个根路径下的动态路由用于匹配所有的页面。我们可以通过 $route.params.pathMatch 获取到传入的参数。

  
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }
  
  
  { path: '/:pathMatch(.*)', name: 'NotFound', component: NotFound }

嵌套路由

有时候,一个页面也可能出现多个组件间相互切换,我们就可以定义子路由来匹配到这个组件。注意:我们也需要在对应的位置设置router-view来渲染子路由匹配到的组件。

如何来定义子路由呢? 我们只需要在路由映射对象中定义一个children属性然后来定义子路由的映射即可。但是有一些注意事项:

  • path属性不需要加/, 如果加上了/,它将映射的是一级路由。

  • 重定向传入的路由是完整的href

{
    path: "/home",
    name: "home",
    component: () => import("../pages/Home.vue"),
    children: [
      {
        path: "",
        redirect: "/home/message"
      },
      {
        path: "message",
        component: () => import("../pages/HomeMessage.vue")
      },
      {
        path: "goods",
        component: () => import("../pages/HomeGoods.vue")
      }
    ]
  }

路由跳转时参数传递

  • 通过params, 即上面讲到的动态路由。
    • 通过$route.params获取

  • 通过query, 当我们跳转路由时,不管是通过router-link还是通过$router.push()等API, 我们都可以传入一个query对象,并且在query对象中设置传递的参数。
    • 通过$route.query获取

  • 通过meta, 在我们定义路由映射表的时候,我们可以在meta字段中定义一些数据。
    • 通过$route.meta获取

  • 通过props,在我们定义路由映射表时,我们可以传递一些props给组件。这个就可以作为组件的props。

    const routes = [
      {
        path: '/search',
        component: SearchUser,
        
        props: route => ({ query: route.query.q }) 
      }
    ]

编程式导航

我们知道,切换路由我们可以通过router-link组件来完成。但是我们大多数还是通过书写代码来切换路由的。

  • 在options API中,我们可以通过vue-router提供的this.$router对象调用其API来切换路由。

  • 在setup中,我们可以通过vue-router提供的hook useRouter来调用其API切换路由。

    setup() {
        const router = useRouter()
        const routerSkip = () => {
          router.push('/about')
          router.replace('/about')
        }
    }

切换路由的API

  • push: 这个方法会向 history 栈添加一个新的记录,可以点击回退上一个路径。

  • replace: 它在导航时不会向 history 添加新记录。取代当前的路径。

  • go: 该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步。

  • back: 在历史堆栈中后退一步。相当于go(-1)

  • forward: 在历史堆栈中前进一步。相当于go(1)router.pushrouter.replacerouter.gowindow.history.pushStatewindow.history.replaceStatewindow.history.go的翻版,它们确实模仿了 window.history 的 API。

注意:前两个API传入的参数和router-link中to属性是一样的。当同时传入pathparams,这会忽略params

动态添加路由

某些情况下我们可能需要动态的来添加路由。比如根据用户不同的权限,注册不同的路由。这个时候我们可以使用一个方法 addRoute

  • 添加一级路由。我们只需要向addRoute中添加一个参数。 如果路由有一个 name字段,并且已经有一个与之名字相同的路由,它会先删除之前的路由。

    
    const categoryRoute = {
      path: "/category",
      component: () => import("../pages/Category.vue")
    }
    
    
    router.addRoute(categoryRoute);

  • 添加二级路由。我们需要传入当前路由(即路由的name字段)和添加的子路由。

    
    router.addRoute("home", {
      path: "moment",
      component: () => import("../pages/HomeMoment.vue")
    })

动态删除路由

与动态添加路由对应。

删除路由有以下三种方式:

导航守卫

vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。在跳转之前,之后等状态时,做一些逻辑处理。类似于Express, Koa中的中间件。

下面我们来讲一讲全局前置守卫router.beforeEach。其他的守卫用法一致,只是调用时机不同而已。

它有三个参数:

  • to:即将进入的路由Route对象。

  • from:即将离开的路由Route对象。

  • next(可选):允许路由正常跳转。在vue-router3.x中,他是必传的,但是在4.x官方已不再建议使用。当不满足条件时,我们可以通过返回一个路径字符串或者一个对象来指定跳转。满足条件时,他会自动跳转。

它有返回值:

  • false:取消当前导航跳转。

  • 不返回或者undefined:进行默认导航跳转。

  • 返回一个路由地址。可以是一个string类型的路径,也可以是一个对象,对象中包含path、query、params等信息。 下面这个例子,就是当用户未登录的时候,不管访问任何页面,我们都让其跳转到login页面。

    router.beforeEach((to, from) => {
      if (to.path !== "/login") {
        const token = window.localStorage.getItem("token");
        if (!token) {
          return "/login"
        }
      }
    })

其他的导航守卫,请访问官网:next.router.vuejs.org/zh/guide/ad…

完整的导航解析流程

  1. 导航被触发。

  2. 在失活的组件里调用 beforeRouteLeave 守卫。

  3. 调用全局的 beforeEach 守卫。

  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。

  5. 在路由配置里调用 beforeEnter

  6. 解析异步路由组件。

  7. 在被激活的组件里调用 beforeRouteEnter

  8. 调用全局的 beforeResolve 守卫(2.5+)。

  9. 导航被确认。

  10. 调用全局的 afterEach 钩子。

  11. 触发 DOM 更新。

  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对于使用 Vite + Vue3 + TypeScript + Pinia + Vue Router + Axios + SCSS 并自动导入 API 的设置,你可以按照以下步骤进行操作: 1. 首先,确保你已经安装了 Node.js,并且版本大于等于 12.0.0。 2. 创建一个新的 Vue 项目,可以使用 Vue CLI 或者手动创建一个空文件夹。 3. 在项目根目录下,打开终端并执行以下命令安装 Vite: ```bash npm init vite@latest ``` 按照提示选择你的项目配置,包括选择 Vue 3、TypeScript 和其他选项。 4. 进入项目目录并安装依赖: ```bash cd your-project-name npm install ``` 5. 安装 Pinia 插件: ```bash npm install pinia ``` 6. 创建一个 `src/store` 目录,并在其中创建 `index.ts` 文件,用于定义和导出你的 Pinia store。 ```typescript // src/store/index.ts import { createPinia } from 'pinia' export const store = createPinia() // 可以在这里定义你的 store 模块 ``` 7. 在项目根目录下创建 `src/api` 目录,用于存放 API 请求相关的文件。 8. 在 `src/api` 目录下创建一个 `index.ts` 文件,用于自动导入所有 API 文件。 ```typescript // src/api/index.ts const modules = import.meta.globEager('./*.ts') const apis: any = {} for (const path in modules) { if (path !== './index.ts') { const moduleName = path.replace(/^.\/|\.ts$/g, '') apis[moduleName] = modules[path].default } } export default apis ``` 这样,你就可以在 `src/api` 目录下创建各种 API 请求的文件,例如 `user.ts`: ```typescript // src/api/user.ts import axios from 'axios' export function getUser(id: number) { return axios.get(`/api/user/${id}`) } ``` 然后,在你的组件中使用自动导入的 API: ```typescript import { defineComponent, ref } from 'vue' import { useUserStore } from '@/store' import apis from '@/api' export default defineComponent({ setup() { const userStore = useUserStore() const userId = ref(1) const fetchUser = async () => { const response = await apis.user.getUser(userId.value) userStore.setUser(response.data) } return { userId, fetchUser, } }, }) ``` 以上就是使用 Vite + Vue3 + TypeScript + Pinia + Vue Router + Axios + SCSS 并自动导入 API 的基本设置。你可以根据自己的需求进一步配置和扩展。希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Web面试那些事儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值