Vue3 - Vuex的简单用法和vue-router路由的简单使用
一. Vuex 的简单使用
Vuex
用来集中式存储管理应用的所有组件的状态。一般用于存储一些公共的变量。例如多语言翻译数据。接下来我们写一个简单的Vuex
案例。首先记得安装一下Vuex依赖:
npm install vuex@next
我们一般会在项目src
目录下创建一个store
文件夹,专门用来放Vuex
相关的文件,我们创建一个index.ts
文件:
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
count: 666
}
},
mutations: {
add (state) {
state.count++
}
}
})
export default store
接着我们在入口处声明这个store
数据管理:
import router from './router/index'
import { createApp } from 'vue'
createApp(App)
.use(store)
.mount('#app')
最后我们就可以在各个组件中,直接使用Vuex
管理的数据了:
<template>
<div @click="add">
{{count}}
</div>
</template>
<script setup>
import { computed } from 'vue'
import {useStore} from 'vuex'
let store = useStore()
let count = computed(()=>store.state.count)
function add(){
store.commit('add')
}
</script>
效果如下:
这里我们需要总结这么几个使用点:
Store
定义方面:
- 我们需要使用来自
vuex
包下的createStore
创建一个数据存储Store
。 - 创建出的
state
变量名称为state
。 - 一般我们在
mutations
属性下,定义一些操作state
变量的动作。而mutation
内部的函数会把state
作为参数。
Store
使用方面:
- 首先入口处需要引用我们自定义的
Store
数据存储。createApp(App).use(store)
- 这样外部组件中,即可直接引用。通过
useStore()
获得全局的store
对象。 - 外部组件如何修改全局的变量?
store.commit('add')
,这里的add
则是在Store
文件中,mutation
属性下定义的函数。
二. vue-router 路由原理
首先说下前端的路由,为了提高页面的交互体验,我们希望用户在跳转不同页面的时候,并不会使整个页面进行刷新。这样就会导致等待时间较长,让交互体验下降。因此目前推出了一种路由模式:
- 用户无论用什么
URL
来访问路由。统一渲染一个前端的入口文件index.html
。 - 然后因为
index.html
文件中加载了页面所需的JS
。 - 这样程序就可以获取当前的页面地址,以及判断当前路由匹配的组件,再去动态渲染当前页面。这样就避免了页面的整体刷新操作。如图:
上面这个流程,也就是所谓的单页应用。它的实现离不开前端路由的运作。因此在这个环节,我们来说下vue-router
路由。我在Vue3的特性介绍以及项目的简单创建这篇文章里面,也简单示例了一下如何使用vue-router
路由。这里挑出几个重点部分来说。
- 我们需要使用
createRouter()
函数来创建路由。 - 区分路由的方式有两种。一种是
hash
模式,通过URL
中#
后面的内容做区分,我们称之为hash-router
。使用createWebHashHistory()
函数来声明。 - 另外一个方式就是
history
模式,在这种方式下,路由看起来和正常的URL
完全一致。使用createWebHistory()
函数来声明。
而我们之前案例中使用的就是哈希路由:
点击页面,URL
为:http://localhost:5173/#/about
如果我们改为createWebHistory()
,那么路由就变成:http://localhost:5173/about
两者的实现区别如图:
先说下Hash
模式的设计:
Hash
模式下,有一个很突出的特点,就是页面在进行跳转的时候,会在URL
上带上一个#
。- 然后在设计上,
hash
值(即URL
的改变)的变化并不会导致浏览器页面的刷新,只是会触发hashchange
事件,这样我们就可以在事件监听中做出对应的动作,触发组件渲染。
再来说下History
模式的设计:
- 浏览器中含有两个
API:pushState
和replaceState
。通过这两个API
我们可以改变URL
地址,并且浏览器不会向后端发送请求,我们就能用另外一种方式实现前端路由 。 - 监听
popstate
事件,然后通过pushState
修改路由的变化。
2.1 手写 vue-router 下 Hash 模式的实现
首先我们在router
目录下,创建一个myRouter
目录,定义一些关键的函数,想一想,我们自定义一个Hash
路由,我们需要哪些函数?
- 能够创建一个
Router
实例,对应的是createRouter
函数。 - 我们还需要指定路由的方式,对应的就是
createWebHashHistory
函数。 - 想一下,我们在使用
Store
的时候,有两个函数的功能是对应的,创建/使用:createStore/useStore
。同理,我们有createRouter
,也需要有useRouter
。这一部分其实用在了vue-router
中注册好的两个内置组件中:router-view
和router-link
。
这俩组件的作用,我们再来回顾一下:
router-view
:用来渲染指定路由对应的组件内容。router-link
:用来指定路由地址的。
那么我们就可以开始编码了。
2.1.1 路由核心代码
import { ref, inject } from 'vue'
const ROUTER_KEY = '__router__'
/**
* 创建路由的函数,介绍一个配置对象
* export default createRouter({
history: createWebHistory(),
routes
})
*/
const createRouter = (options: any) => {
return new Router(options)
}
// 获取路由实例
const useRouter = () => {
return inject(ROUTER_KEY)
}
const createWebHashHistory = () => {
// 监听 hashchange 事件
function bindEvents(fn: any) {
window.addEventListener('hashchange', fn)
}
return {
bindEvents,
url: window.location.hash.slice(1) || '/'
}
}
class Router {
// 路由模式
private history;
// 路由配置数组
private routes: any[];
// 当前路由
private currentPath;
constructor(options: any) {
this.history = options.history
this.routes = options.routes
// ref 用来将当前地址的值封装为响应式
this.currentPath = ref(this.history.url)
this.history.bindEvents(() => {
this.currentPath.value = window.location.hash.slice(1)
})
}
install(app: any) {
app.provide(ROUTER_KEY, this)
}
}
export { createRouter, createWebHashHistory, useRouter }
2.1.2 自定义 router-view
根据上文的用处介绍,我们这样设计这个组件RouterVIew
:
- 通过
useRouter
获取当前路由的实例,这样就可以拿到当前的路由了,也就是router.currentPath.value
的值。 - 在用户路由配置
route
中计算出匹配的组件resultComponent
,在template
内部使用:is
来指定component
组件动态渲染。
代码如下:
<template>
<component :is="resultComponent"></component>
</template>
<script setup>
import { computed } from "vue";
// 引入自定义的组件
import { useRouter } from "../router/myRouter/index";
// 获取自定义的路由实例,希望通过它获得当前路由 currentPath
let router = useRouter();
const resultComponent= computed(() => {
const route = router.routes.find(
(route) => route.path === router.currentPath.value
);
return route?.component ?? null;
});
</script>
备注:看下我们外部对于routes
数组的定义,这样结合上面代码来看就会容易理解很多。
2.1.3 自定义 router-link
根据上文的用处介绍,我们这样设计这个组件RouterLink
:
- 根据案例的使用,我们知道
router-link
组件一般会传入一个属性:to
,用于指定路由值。 - 我们改变URL的时候,会在
URL
上拼接一个#
,因此我们这个组件的核心功能点就是两个。一个是接收父组件传入的值,传给a标签,一个是完成#
的拼接。
代码实现如下:
<template>
<a :href="'#' + props.to">
<slot />
</a>
</template>
<script setup>
let props = defineProps({
to: { type: String, required: true },
});
</script>
最后,我们还需要把自定义的两个属性注册到全局组件中,我们在入口main.ts
中增加组件的注册:
import RouterLink from './components/RouterLink.vue'
import RouterView from './components/RouterView.vue'
createApp(App)
.component('MyComponent', MyComponent)
.component('myRouter-link', RouterLink)
.component('myRouter-view', RouterView)
.use(router)
.use(store)
.mount('#app')
那么我们首页就可以这么改:
<template>
<div>
<myRouter-link to="/">首页</myRouter-link> |
<myRouter-link to="/about">关于</myRouter-link>
</div>
<myRouter-view></myRouter-view>
</template>
效果如下:(记得关注URL
的改变)
这样我们在改变路由的时候,就会根据绑定的事件,动态的改变响应式数据currentPath
:
这样一个简单的vue-router
(Hash
模式)组件就完成了。主要还是依赖于这两个API
:
API:location.hash
Event:hashchange