手写一个 Vuex 和 Vue-Router

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

概述


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

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

1682522413(1).png


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 的作用,比如网站托管在 example.com,base 就设置为/;托管在 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) {
  // 报错
}
复制代码

去除了 router-link 的部分属性

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 对象

相关文章
|
JavaScript
Vue Vuex 热更新
Vue Vuex 热更新
65 1
【vue2项目总结】—— vuex相关
【vue2项目总结】—— vuex相关
60 0
|
3月前
|
JavaScript
Vue3基础(四)___Vue-Router
本文介绍了在Vue 3中如何使用`vue-router@4`进行路由管理,包括安装路由库、定义路由配置、在组件中使用`useRouter`和`useRoute`钩子函数,以及如何在组件中进行路由跳转和获取路由参数。
67 1
Vue3基础(四)___Vue-Router
|
7月前
|
存储 资源调度 JavaScript
vue引入vuex
vue引入vuex
64 7
|
JavaScript
Vue Vuex 学习
Vue Vuex 学习
63 0
|
7月前
|
存储 JavaScript
vue3中如何使用vuex
首先,在这里回答一个粉丝的问题,为什么有local storage 和session storage还要使用vuex,这里我解释一下,我们要明白浏览器的存储和vuex的存储的不同点,首先,浏览器存在本地,vuex存在内存里,所以vuex刷新会丢失,从现在来看,好像存在浏览器里会更好,但是,有个问题时,存在浏览器里不是响应式的啊,只有存在vuex才是响应式啊,我们通过计算属性或者watch监听下在同步更新下就行,但是,浏览器不支持响应式,所以,我们为了规避掉vuex的的刷新丢失的问题,所以,当刷新时,从浏览器的存储里拿数据,防止在vuex里面,在传给组件,借助vuex的特性,来实本地存储响应式。
【Vue2.0】—vue-router(二十七)
【Vue2.0】—vue-router(二十七)
|
存储 前端开发 JavaScript
【Vue2.0】—vue-router(二十六)
【Vue2.0】—vue-router(二十六)
|
存储 JavaScript API
Vue3 :Pinia入门
Vue3 :Pinia入门
146 2