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 成功并完全运行后触发。// 它等待着任何返回的 promiseafter((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/。