概述

在 Vue3.0 全面开放的大背景下,Vue 的周边生态迅速跟进,其中与 Vue 具有“血缘关系”的两个组件Vuex 和 Vue-Router 也相对应 Vue3.0 推出了全新的版本。

此次版本更新中,Vuex 大部分API都与之前的版本(Vuex3.0)相似,只有小部分发生了变动,Vue-Router相对来说发生了较大的变化

image-20211207202613780

Vuex

Vuex 的变更首先体现在挂载方式和创建过程上,不再使用 Vuex.store 来实例化 store,直接使用 createStore 来创建 store

对比一下当前版本与上一版的区别

// vuex3
// /store/index.js
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 1
  }
})

export default store
// main.js
import store from './store'
new Vue({store, render: h => h(App)}).$mount('#app')

// vuex4
// /store/index.ts
import { createStore, Store, useStore as baseUseStore } from 'vuex';
import { InjectionKey } from 'vue';
// import { moduleA } from "@/store/moduleA";

export interface State {
  count: number;
  // modules: {
  //   a: ReturnType<typeof moduleA>;
  // };
}

export const key: InjectionKey<Store<State>> = Symbol();

export const store = createStore<State>({
  state: {
    count: 1,
  },
  mutations: {
    COUNT_ADD(state) {
      state.count++;
    },
  },
  getters: {
    count(state) {
      return state.count;
    },
  },
  // modules: {
  //   a: moduleA
  // }
});

// 自定义 useStore
// 通过引入自定义的组合式函数,不用提供 injection key 和类型声明就可以直接得到类型化的 store
export function useStore() {
  return baseUseStore(key);
}
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { key, store } from './store';

const app = createApp(App);

app.use(router)
  .use(store, key)
  .mount('#app');

注意:在 main.ts 挂载 store 的时候必须传入 key,获取 store 的时候使用 key 来获取,本质上,Vuex 将store 安装到 Vue 应用中使用了 Vue 的 Provide/Inject 特性,具体原理不在此处介绍

在使用时,由于没有将$store 挂载到 Vue 实例上,所以要是用 useStore 获取 store

<template>
  <div class="about">
    {{count}}
    <button @click="countAdd">+</button>
  </div>
</template>

<script setup lang="ts">
import { useStore } from "@/store";
import { computed } from "vue";

const store = useStore()
const count = computed(() => store.getters.count)
const countAdd = () => store.commit('COUNT_ADD')
</script>

这里需要将获取的值使用 computed包裹一下,否则不会实现响应式

Vuex4 的 typescript 类型支持并不是很友好,很多时候需要使用者自定义类型,这也是 vuex在社区中口碑急转直下的原因。

Vue-Router

初始化

第一点同 Vuex,同样是使用使用函数式代替了原有的类,不再使用原先的 new VueRouter 来创建路由,而是使用 createRouter 创建路由

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/about',
      name: 'About',
      component: () => import('../views/About.vue')
    }
  ]
})

路由模式由原来的 mode 换成了 history,值的类型从字符串变成了函数,替换关系如下:

  • "history": createWebHistory()
  • "hash": createWebHashHistory()
  • "abstract": createMemoryHistory()

原本的 base 项现在变成了 history 函数的第一个参数(代码中的 import.meta.env 是 Vite 提供的能力)

base 的作用,比如网站托管在 https://example.com,base 就设置为/;托管在 https://example.com/app/ 下,base 就是/app/

通配路由

再捕获 Not Found 的时候,原先的方法时使用*方道路有最后来匹配未命中的路由,在新版本中,Vue-Router 实现了自己的路由逻辑

// 原来的通配符
{path: '*'}
// 现在的通配
{path: '/:pathMatch(.*)*'}
// 配置 /user-开头的路由
{path: '/user-*'}
// 匹配 user= 开头的路由,可以通过$route.params.afterUser获取后面的值
{path: '/user-:afterUser(.*)'}

onReady 替换为 isReady

// 将
router.onReady(onSuccess, onError)
// 替换成
router.isReady().then(onSuccess).catch(onError)
// 或者使用 await:
try {
  await router.isReady()
  // 成功
} catch (err) {
  // 报错
}
append

在 Vue-Router3.0 中,可以通过 append 属性实现追加路由的效果,如当前路由是在/app/下,点击这个 router-link 会就会跳转到/app/append

<router-link :to="{ path: 'append'}" append></router-link>

在Vue-Router4.0中, append 属性被移除(因为使用量不大,而且用户很容易实现这个效果),但是可以通过以下方法来实现相同的效果

<router-link :to="append(currentPath, 'child-route')">
  append
</router-link>
tag

在 Vue-Router3.0 中,router-link 会默认渲染为 a 标签,可以通过使用 tag 属性来控制渲染的标签类型

<router-link to="/about" tag="span">About</router-link>

渲染出来的效果就是使用 span 标签

在新版中需要使用 s-slot 来实现 非默认tag渲染

<router-link to="/about" custom v-slot="{ navigate }">
  <span>About</span>
</router-link>
event

在 Vue-Router3.0 中,可以通过event 属性来控制触发 router-link 的事件,如

<router-link to="/about" event="mouseover">About</router-link>

在新版中同样需要使用 v-slot 来实现

<router-link to="/about" custom v-slot="{ navigate }">
  <span @mouseover="navigate" role="link">About</span>
</router-link>
exect

Vue-Router3.0 中使用 exect 属性来控制路由匹配时 router-link 显示为激活状态,新版本中移除了这个属性,现在的路由是基于它们所代表的路由记录来激活的,而不是路由地址对象及其 pathqueryhash 属性来激活的

router-view

transitionkeep-alive 现在必须通过 v-slot API 在 RouterView 内部使用:

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

composition-api

为我们在 setup 里面没有访问 this,所以我们使用 route 或 router 时需要通过 useRoute 和 useRouter 来获取 route 和 router 对象


前端小白