• VUE3照本宣科——路由与状态管理器



    VUE3照本宣科系列导航

    1.VUE3照本宣科——认识VUE3
    2.VUE3照本宣科——应用实例API与setup
    3.VUE3照本宣科——响应式与生命周期钩子
    4.VUE3照本宣科——内置指令与自定义指令及插槽
    5.VUE3照本宣科——路由与状态管理器
    6.VUE3照本宣科——eslint与prettier
    7.VUE3照本宣科——package.json与vite.config.js

    前言

    👨‍💻👨‍🌾📝记录学习成果,以便温故而知新

    “VUE3照本宣科”是指照着中文官网菜鸟教程这两个“本”来学习一下VUE3。以前也学过VUE2,当时只在gitee留下一些代码,却没有记录学习的心得体会,有时也免不了会追忆一下。

    以后出现“中文官网”不做特殊说明就是指:https://cn.vuejs.org/;菜鸟教程就是指:https://www.runoob.com/vue3/vue3-tutorial.html


    一、路由(router)

    Vue.js 的官方路由的内容洋洋洒洒,蔚为大观,内容太多,现在介绍zbxk项目中路由的默认内容及其它常用项。

    Vue Router 是 Vue.js 的官方路由。它与 Vue.js 核心深度集成,让用 Vue.js 构建单页应用变得轻而易举。

    所以说路由与Vue是独立的,需要另外安装:

    npm install vue-router@4
    
    • 1

    main.js中安装路由代码:

    import router from './router'
    
    app.use(router)
    
    • 1
    • 2
    • 3

    1.createRouter

    创建一个可以被 Vue 应用使用的 Router 实例。

    在Vue.js 的官方路由文档中找到如下代码,感觉很短小经精悍:

    // 1. 定义路由组件.
    // 也可以从其他文件导入
    const Home = { template: '
    Home
    '
    } const About = { template: '
    About
    '
    } // 2. 定义一些路由 // 每个路由都需要映射到一个组件。 // 我们后面再讨论嵌套路由。 const routes = [ { path: '/', component: Home }, { path: '/about', component: About }, ] // 3. 创建路由实例并传递 `routes` 配置 // 你可以在这里输入更多的配置,但我们在这里 // 暂时保持简单 const router = VueRouter.createRouter({ // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。 history: VueRouter.createWebHashHistory(), routes, // `routes: routes` 的缩写 }) // 5. 创建并挂载根实例 const app = Vue.createApp({}) //确保 _use_ 路由实例使 //整个应用支持路由。 app.use(router) app.mount('#app') // 现在,应用已经启动了!
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    2.router-link

    使用一个自定义组件 router-link 来创建链接。这使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。

    在Vue.js 的官方路由文档实在是没有找组件 router-link更详细的介绍,而菜鸟教程却更全面一点。

    1.to 表示目标路由的链接。 当被点击后,内部会立刻把 to 的值传到 router.push(),所以这个值可以是一个字符串或者是描述目标位置的对象。
    2.replace 设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push(),导航后不会留下 history 记录。
    3.append 设置 append 属性后,则在当前 (相对) 路径前添加其路径。例如,我们从 /a 导航到一个相对路径 b,如果没有配置 append,则路径为 /b,如果配了,则为 /a/b
    4.tag 有时候想要 渲染成某种标签,例如

  • 。 于是我们使用 tag prop 类指定何种标签,同样它还是会监听点击,触发导航。
    5.active-class 设置 链接激活时使用的 CSS 类名。可以通过以下代码来替代。
    6. exact-active-class 配置当链接被精确匹配的时候应该激活的 class。可以通过以下代码来替代。
    7.event 声明可以用来触发导航的事件。可以是一个字符串或是一个包含字符串的数组。

  • 
    <router-link to="home">Homerouter-link>
    
    <a href="home">Homea>
    
    
    <router-link v-bind:to="'home'">Homerouter-link>
    
    
    <router-link :to="'home'">Homerouter-link>
    
    
    <router-link :to="{ path: 'home' }">Homerouter-link>
    
    
    <router-link :to="{ name: 'user', params: { userId: 123 }}">Userrouter-link>
    
    
    <router-link :to="{ path: 'register', query: { plan: 'private' }}">Registerrouter-link>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在代码里使用编程式导航:

    // 字符串路径
    router.push('/users/eduardo')
    
    // 带有路径的对象
    router.push({ path: '/users/eduardo' })
    
    // 命名的路由,并加上参数,让路由建立 url
    router.push({ name: 'user', params: { username: 'eduardo' } })
    
    // 带查询参数,结果是 /register?plan=private
    router.push({ path: '/register', query: { plan: 'private' } })
    
    // 带 hash,结果是 /about#team
    router.push({ path: '/about', hash: '#team' })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    注意:如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。

    3.router-view

    将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局。

    <div id="app">
      <h1>Hello App!h1>
      <p>
        
        
        
        <router-link to="/">Go to Homerouter-link>
        <router-link to="/about">Go to Aboutrouter-link>
      p>
      
      
      <router-view>router-view>
    div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    以上代码也来源于Vue.js 的官方路由文档,说明router-link与router-view的使用。

    4.useRoute

    返回当前的路由地址。相当于在模板中使用 $route。

    地址相关信息:

    • $route.fullPath 路由完整路径
    • $route.matched 路由匹配路径
    • $route.meta 路由元数据
    • $route.name 路由名称
    • $route.params 路由参数,params传参
    • $route.path 路由路径
    • $route.query 路由参数,query传参
      其它相关信息就不一一罗列了。

    5.useRouter

    返回路由器实例。相当于在模板中使用 $router。

    演示代码:

    script setup>
    import { onMounted } from 'vue'
    import { useRoute, useRouter } from 'vue-router'
    
    const route = useRoute()
    const router = useRouter()
    
    onMounted(() => {
      console.log(route)
      console.log(router)
    })
    
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    终端输出如图:
    在这里插入图片描述

    6.路由守卫

    官方文档中称为“导航守卫 ”。
    可以使用 router.beforeEach 注册一个全局前置守卫:

    const router = createRouter({ ... })
    
    router.beforeEach((to, from) => {
      // ...
      // 返回 false 以取消导航
      return false
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    接收两个参数:

    • to: 即将要进入的目标 用一种标准化的方式。
      from: 当前导航正要离开的路由 用一种标准化的方式

    返回:

    • false: 取消当前的导航。
    • 一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像你调用 router.push() 一样,你可以设置诸如 replace: true 或 name: ‘home’ 之类的配置。

    7.嵌套路由

    示例代码:

    const routes = [
      {
        path: '/user/:id',
        component: User,
        children: [
          {
            // 当 /user/:id/profile 匹配成功
            // UserProfile 将被渲染到 User 的  内部
            path: 'profile',
            component: UserProfile,
          },
          {
            // 当 /user/:id/posts 匹配成功
            // UserPosts 将被渲染到 User 的  内部
            path: 'posts',
            component: UserPosts,
          },
        ],
      },
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    嵌套路由一般可以实现布局。

    二、状态管理器(Pinia)

    zbxk项目是用的pinia作为状态管理器,pinia官网中说:

    Pinia是Vue 的专属状态管理库,它允许你跨组件或页面共享状态。

    使用 Pinia可以获得如下功能:

    • Devtools 支持
      • 追踪 actions、mutations 的时间线
      • 在组件中展示它们所用到的 Store
      • 让调试更容易的 Time travel
    • 热更新
      • 不必重载页面即可修改 Store
      • 开发时可保持当前的 State
    • 插件:可通过插件扩展 Pinia 功能
    • 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。
    • 支持服务端渲染

    Pinia与Vue也是独立的,需要另外安装:

    npm install pinia
    
    • 1

    main.js中安装pinia代码:

    import { createPinia } from 'pinia'
    
    app.use(createPinia())
    
    • 1
    • 2
    • 3

    1.定义Store

    Store应该是保存状态的容器,但是至今没有看到过官方文档的明确说明,也可能是自己看官方文档不够仔细,搞得Store总是是是而非的。

    Store是用 defineStore() 定义的,它的第一个参数被用作 id是必须传入的;第二个参数可接受两类值:Setup 函数或 Option 对象。

    (1)Option Store

    官方示例代码:

    export const useCounterStore = defineStore('counter', {
      state: () => ({ count: 0 }),
      getters: {
        doubleCount: (state) => state.count * 2,//稍有改动
      },
      actions: {
        increment() {
          this.count++
        },
      },
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。

    以上介绍是不是熟悉的配方、熟悉的味道?Vue2使用Vuex管理状态时貌似也是这样定义的。

    (2)Setup Store

    官方示例代码:

    export const useCounterStore = defineStore('counter', () => {
      const count = ref(0)
      const doubleCount = computed(() => count.value * 2)//这行代码官方示例中没有,zbxk项目默认代码里是有的
      function increment() {
        count.value++
      }
    
      return { count, doubleCount, increment }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在 Setup Store 中:

    • ref() 就是 state 属性
    • computed() 就是 getters
    • function() 就是 actions

    对比Option Store与Setup Store的示例代码,发现它们是殊途同归。

    2.State

    (1)访问state

    示例代码:

    const store = useStore()
    
    store.count++
    
    • 1
    • 2
    • 3

    (2)重置state

    示例代码:

    const store = useStore()
    
    store.$reset()
    
    • 1
    • 2
    • 3

    (3)变更state

    调用 $patch方法变更state。
    示例代码一:

    store.$patch({
      count: store.count + 1,
      age: 120,
      name: 'DIO',
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    示例代码二:

    store.$patch((state) => {
      state.items.push({ name: 'shoes', quantity: 1 })
      state.hasChanged = true
    })
    
    • 1
    • 2
    • 3
    • 4

    示例代码二明显要比示例代码一更灵活。

    (4)替换state

    示例代码:

    // 这实际上并没有替换`$state`
    store.$state = { count: 24 }
    // 在它内部调用 `$patch()`:
    store.$patch({ count: 24 })
    
    • 1
    • 2
    • 3
    • 4

    这个感觉就是变更,写法上不一样

    (5)订阅state

    示例代码:

    cartStore.$subscribe((mutation, state) => {
      // import { MutationType } from 'pinia'
      mutation.type // 'direct' | 'patch object' | 'patch function'
      // 和 cartStore.$id 一样
      mutation.storeId // 'cart'
      // 只有 mutation.type === 'patch object'的情况下才可用
      mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
    
      // 每当状态发生变化时,将整个 state 持久化到本地存储。
      localStorage.setItem('cart', JSON.stringify(state))
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    本来以为这就是侦听的,看到pinia官网上所说:

    比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次。
    才发觉这是订阅的patch。

    3.Getter

    (1)使用Getter

    示例代码:

    <script setup>
    import { useUserListStore } from './store'
    const userList = useUserListStore()
    const { getUserById } = storeToRefs(userList)
    // 请注意,你需要使用 `getUserById.value` 来访问
    //