VUE3照本宣科——路由与状态管理器
- 前言
- 一、路由(router)
- 1.createRouter
- 2.router-link
- 3.router-view
- 4.useRoute
- 5.useRouter
- 6.路由守卫
- 7.嵌套路由
- 二、状态管理器(Pinia)
- 1.定义Store
- (1)Option Store
- (2)Setup Store
- 2.State
- (1)访问state
- (2)重置state
- (3)变更state
- (4)替换state
- (5)订阅state
- 3.Getter
- (1)使用Getter
- 4.Action
- (1)使用Action
- (2)订阅action
前言
👨💻👨🌾📝记录学习成果,以便温故而知新
“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
main.js中安装路由代码:
import router from './router'
app.use(router)
1.createRouter
创建一个可以被 Vue 应用使用的 Router 实例。
在Vue.js 的官方路由文档中找到如下代码,感觉很短小经精悍:
// 1. 定义路由组件.
// 也可以从其他文件导入
const Home = { template: '<div>Home</div>' }
const About = { template: '<div>About</div>' }
// 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')
// 现在,应用已经启动了!
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 有时候想要 <router-link> 渲染成某种标签,例如<li>。 于是我们使用 tag prop 类指定何种标签,同样它还是会监听点击,触发导航。
5.active-class 设置 链接激活时使用的 CSS 类名。可以通过以下代码来替代。
6. exact-active-class 配置当链接被精确匹配的时候应该激活的 class。可以通过以下代码来替代。
7.event 声明可以用来触发导航的事件。可以是一个字符串或是一个包含字符串的数组。
<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 渲染结果 -->
<a href="home">Home</a>
<!-- 使用 v-bind 的 JS 表达式 -->
<router-link v-bind:to="'home'">Home</router-link>
<!-- 不写 v-bind 也可以,就像绑定别的属性一样 -->
<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>
在代码里使用编程式导航:
// 字符串路径
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' })
注意:如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。
3.router-view
将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局。
<div id="app">
<h1>Hello App!</h1>
<p>
<!--使用 router-link 组件进行导航 -->
<!--通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link to="/">Go to Home</router-link>
<router-link to="/about">Go to About</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
以上代码也来源于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>
终端输出如图:
6.路由守卫
官方文档中称为“导航守卫 ”。
可以使用 router.beforeEach 注册一个全局前置守卫:
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
接收两个参数:
- to: 即将要进入的目标 用一种标准化的方式。
from: 当前导航正要离开的路由 用一种标准化的方式
返回:
- false: 取消当前的导航。
- 一个路由地址: 通过一个路由地址跳转到一个不同的地址,就像你调用 router.push() 一样,你可以设置诸如 replace: true 或 name: ‘home’ 之类的配置。
7.嵌套路由
示例代码:
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
// 当 /user/:id/profile 匹配成功
// UserProfile 将被渲染到 User 的 <router-view> 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 <router-view> 内部
path: 'posts',
component: UserPosts,
},
],
},
]
嵌套路由一般可以实现布局。
二、状态管理器(Pinia)
zbxk项目是用的pinia作为状态管理器,pinia官网中说:
Pinia是Vue 的专属状态管理库,它允许你跨组件或页面共享状态。
使用 Pinia可以获得如下功能:
- Devtools 支持
- 追踪 actions、mutations 的时间线
- 在组件中展示它们所用到的 Store
- 让调试更容易的 Time travel
- 热更新
- 不必重载页面即可修改 Store
- 开发时可保持当前的 State
- 插件:可通过插件扩展 Pinia 功能
- 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。
- 支持服务端渲染
Pinia与Vue也是独立的,需要另外安装:
npm install pinia
main.js中安装pinia代码:
import { createPinia } from 'pinia'
app.use(createPinia())
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++
},
},
})
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 }
})
在 Setup Store 中:
- ref() 就是 state 属性
- computed() 就是 getters
- function() 就是 actions
对比Option Store与Setup Store的示例代码,发现它们是殊途同归。
2.State
(1)访问state
示例代码:
const store = useStore()
store.count++
(2)重置state
示例代码:
const store = useStore()
store.$reset()
(3)变更state
调用 $patch方法变更state。
示例代码一:
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
示例代码二:
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
示例代码二明显要比示例代码一更灵活。
(4)替换state
示例代码:
// 这实际上并没有替换`$state`
store.$state = { count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({ count: 24 })
这个感觉就是变更,写法上不一样
(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))
})
本来以为这就是侦听的,看到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` 来访问
// <script setup> 中的函数
</script>
<template>
<p>User 2: {{ getUserById(2) }}</p>
</template>
重点看一下storeToRefs,pinia官网说:
创建一个引用对象,包含 store 的所有 state、 getter 和 plugin 添加的 state 属性。 类似于 toRefs(),但专门为 Pinia store 设计, 所以 method 和非响应式属性会被完全忽略。
4.Action
(1)使用Action
示例代码:
const store = useCounterStore()
// 将 action 作为 store 的方法进行调用
store.randomizeCounter()
其它的应用都是围绕着这段代码的核心思想展开的。
(2)订阅action
通过 store.$onAction() 来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。
const unsubscribe = someStore.$onAction(
({
name, // action 名称
store, // store 实例,类似 `someStore`
args, // 传递给 action 的参数数组
after, // 在 action 返回或解决后的钩子
onError, // action 抛出或拒绝的钩子
}) => {
// 为这个特定的 action 调用提供一个共享变量
const startTime = Date.now()
// 这将在执行 "store "的 action 之前触发。
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 这将在 action 成功并完全运行后触发。
// 它等待着任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回一个拒绝的 promise,这将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动删除监听器
unsubscribe()
本系列专题组合式外的功能介绍得较少,如mapState与mapWritableState等功能请参看pinia官网https://pinia.vuejs.org/zh/。