神奇的Vue3
第一章 神奇的Vue3—基础篇
文章目录
前言(为什么选择Pinia而不是Vuex)
- 简单性和直观性:
Pinia
的 API 更简单、更直观,使得使用存储变得轻而易举。另外Pinia
的结构与Vue的Options API 类似,因此更容易上手。 - 响应式状态管理:
Pinia
的状态可以直接在操作中进行更新
,从而减少了冗余代码。与 Vuex 不同,Pinia 不再需要显式的提交mutation。 - 模块化设计:
Pinia
是模块化设计的,允许创建多个存储库
,这些存储库可以直接在需要它们的组件中导入。这样可以更好地进行代码拆分,并提供更好的TypeScript
推断。 - 开发者工具支持:
Pinia
具有内置的开发者工具支持,与 Vuex 的开发者工具类似,可以更好地调试和跟踪状态的变化。 - TypeScript 支持:
Pinia
提供了更好的TypeScript
支持,使得开发过程更加轻松。
总的来说,Pinia
提供了更简单、更直观的 API,更好的模块化设计以及更好的 TypeScript
支持,使得它成为 Vue 3 应用程序的首选状态管理库。当然,对于已经在使用 Vuex 的项目来说,迁移到 Pinia 可能需要一些工作,但对于新项目来说,Pinia 是一个更现代化、更简单的选择。
一、Pinia是什么?
Pinia
是一个用于 Vue 3
的状态管理库,旨在提供简单而强大的方式来管理应用程序的状态。它建立在 Vue 3
的响应性 API 之上,为大型应用程序中复杂状态的管理提供了简单而灵活的架构。Pinia 提供了一种存储模式
,允许定义用于管理特定应用程序状态的数据存储。这使得组织应用程序状态变得简单,并且可以轻松地在组件之间共享状态。
二、使用步骤
1.引入Pinia
在项目中设置和安装 Pinia
yarn add pinia
# 或者使用 npm
npm install pinia
2.创建一个 Pinia
实例并将其注册到 Vue 应用中:
代码如下(示例):
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
3.创建存储库(Store):
- 在存储库文件夹内创建一个新文件,例如 counter.ts。
- 在文件中导入 defineStore 和 ref:
import { ref } from 'vue';
import { defineStore } from 'pinia';
- 使用 defineStore 创建存储库实例,并按照 Pinia 的命名约定命名存储库:
// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 use 开头且以 `Store` 结尾。
// (比如 `useUserStore`,`useCartStore`,`useProductStore`)
// 第一个参数:是你的应用中 Store 的唯一 ID。
// 第二个参数:可接受两类值:Setup 函数或 Option 对象。
export const useCounterStore = defineStore("counter", () => {
// 在此定义状态、getter 和 action
return {};
});
4.创建Store的两种语法
(1)Option Store
与 Vue 选项式 API(Option API) 类似,我们也可以传入一个带有 state
、actions
与 getters
属性的 Option 对象
- state 是 store 的数据 (data)
- getters 是 store 的计算属性 (computed)
- actions 是方法 (methods)。
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
(2)Setup Store
与 Vue 组合式 API(Composition API) 的 setup 函数
相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
- ref() 就是 state 属性
- computed() 就是 getters
- function() 就是 actions
注意,要让 pinia 正确识别 state,必须在 setup store 中返回 state 的所有属性
。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
5.使用 Store
我们在使用 <script setup>
调用 useStore()
(或者使用 setup() 函数
,像所有的组件那样) 之前,store 实例是不会被创建的:
store
被实例化后,可以直接访问在store
的state
、getters
和actions
中定义的任何属性
<script setup>
import { useCounterStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量
const store = useCounterStore()
console.log(store);
</script>
三、Store核心
1. State
state 被定义为一个返回初始状态的函数
import { defineStore } from 'pinia'
interface UserInfo {
name: string
age: number
}
const useStore = defineStore('storeId', () => {
// 箭头函数可以进行完整类型推理,推荐使用
// 去掉return省略写法:
// state: () => ({})
state: () => {
return {
// 所有这些属性都将自动推断出它们的类型
count: 0,
name: 'Eduardo',
isAdmin: true,
// 用于初始化空列表
userList: [] as UserInFfo[],
// 用于尚未加载的数据
user: null as UserInfo | null,
}
}
});
(1) 引入访问state
具体操作见本文:二 - 5
const store = useStore();
// 直接操作state
store.count++;
(2) 重置state
store.$reset()
(3) 同时修改多个state属性
store.$patch({
count: store.count + 1,
age: 120,
isAdmin: false,
})
(4) 订阅(监听)state
import { MutationType } from 'pinia'
const store = useStore();
store.$subscribe((mutation: MutationType, state) => {
mutation.type // 'direct' | 'patch object' | 'patch function'
// 和 store.$id 一样
mutation.storeId // 'storeId'
// 只有 mutation.type === 'patch object'的情况下才可用
mutation.payload // 传递给 cartStore.$patch() 的补丁对象。
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('cart', JSON.stringify(state))
})
(5) 整个项目的pinia监听
// main.ts 具体见本文 二 - 2
const pinpa = createPinia();
watch(
pinia.state,
(state) => {
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
2. Getter
Getter
完全等同于 store 的 state 的计算值
。可以通过 defineStore() 中的 getters属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数
:
可以通过
this
访问到整个store
实例,但(在 TypeScript 中)必须定义返回类型。
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 2,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
})
// 在组件中使用
import { useCounterStore } from './counterStore'
const store = useCounterStore()
store.doubleCount; // 注意这里使用的不是方法不需要加`()`;
3. Action
Action
相当于组件中的method
,可以通过defineStore()
中的actions
属性来定义
action
也可通过this
访问整个store
实例,并支持完整的类型标注(以及自动补全)。与Getter不同的是
,action
可以是异步
的,可以在它们里面await
调用任何 API,以及其他action
!
import { mande } from 'mande'
const api = mande('/api/users')
export const useUsers = defineStore('users', {
state: () => ({
userData: null,
// ...
}),
actions: {
async registerUser(login, password) {
try {
this.userData = await api.post({ login, password })
showTooltip(`Welcome back ${this.userData.name}!`)
} catch (error) {
showTooltip(error)
// 让表单组件显示错误
return error
}
},
},
})
// 组件中调用
const store = useUsers()
// 将 action 作为 store 的方法进行调用
store.registerUser("login", "password");
(1) 订阅(监听)action
const unsubscribe = store.$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()
总结
总的来说,Pinia
是一个现代化、简单、灵活且功能强大的状态管理库,适用于 Vue 3 项目,为我们提供了更好的状态管理解决方案。通过使用 Pinia
,可以更轻松地管理应用程序的状态,提高开发效率并改善代码质量。