开始
安装
npm install pinia
注册插件
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')
核心概念
定义store
Store()的定义是通过defineStore(),它的第一个参数要求是一个唯一不可重复的名字
import { defineStore } from 'pinia'
// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// 第一个参数是你的应用中 Store 的唯一 名字。
export const useXXXXXStore = defineStore('unique', {
// 其他配置...
})
-
第一个参数
- 必须传入的, Pinia 将用它来连接 store 和 devtools
-
第二个参数
- 可接受两类值:Setup 函数或 Option 对象
Option Store
Vue 的选项式 API 类似
可以传入一个带有 state、actions 与 getters 属性的 Option 对象
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
state是 store 的数据 (data)getters是 store 的计算属性 (computed)actions则是方法 (methods)
Setup Store
Vue 组合式 API 的 setup 函数相似
- 我们可以传入一个函数,该函数定义了一些响应式属性和方法
- 并且返回一个带有我们想暴露出去的属性和方法的对象
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})
在 Setup Store 中
ref()就是state属性computed()就是gettersfunction()就是actions
注意,要让 pinia 正确识别 state,你必须在 setup store 中返回 state 的所有属性。这意味着,你不能在 store 中使用私有属性。
两者有什么区别
-
Setup store 比 Option Store带来了更多的灵活性
- 因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数
-
Setup store 也可以依赖于全局提供的属性,比如路由。任何应用层面提供的属性都可以在 store 中使用
inject()访问,就像在组件中一样
import { inject } from 'vue'
import { useRoute } from 'vue-router'
import { defineStore } from 'pinia'
export const useSearchFilters = defineStore('search-filters', () => {
const route = useRoute()
// 这里假定 `app.provide('appProvided', 'value')` 已经调用过
const appProvided = inject('appProvided')
// ...
return {
// ...
}
})
你应该选用哪种语法?
两种语法都有各自的优势和劣势。Option Store 更容易使用,而 Setup Store 更灵活和强大
使用 Store
<script setup>
import { useXXXXXStore } from '@/stores/counter'
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useXXXXXStore()
</script>
-
请注意,
store是一个用reactive包装的对象,这意味着不需要在 getters 后面写.value -
就像
setup中的props一样,我们不能对它进行解构- 解构会破环它的响应式
<script setup>
import { useCounterStore } from '@/stores/counter'
import { computed } from 'vue'
const store = useCounterStore()
// ❌ 这将不起作用,因为它破坏了响应性
// 这就和直接解构 `props` 一样
const { name, doubleCount } = store
name // 将始终是 "Eduardo"
doubleCount // 将始终是 0
setTimeout(() => {
store.increment()
}, 1000)
// ✅ 这样写是响应式的
// 💡 当然你也可以直接使用 `store.doubleCount`
const doubleValue = computed(() => store.doubleCount)
</script>
从 Store 解构
保持响应式需要使用storeToRefs()
- 当你只使用 store 的状态而不调用任何 action 时,它会非常有用
<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>
State
基本使用
相当于组件的状态
- state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。
import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 为了完整类型推理,推荐使用箭头函数
state: () => {
return {
// 所有这些属性都将自动推断出它们的类型
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
})
某些情况下可能推断不出来,只能自己定义接口了
interface State {
userList: UserInfo[]
user: UserInfo | null
}
interface UserInfo {
name: string
age: number
}
const useStore = defineStore('storeId', {
state: (): State => {
return {
userList: [],
user: null,
}
},
})
访问 state
- 默认情况下,你可以通过
store实例访问 state,直接对其进行读写 - 如果没有在
state()中被定义,则不能被添加
const store = useStore()
store.count++
重置state
通过调用 store 的 $reset() 方法将 state 重置为初始值
使用选项式 API 时
const store = useStore()
store.$reset()
在 Setup Stores中
需要创建自己的 $reset() 方法
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function $reset() {
count.value = 0
}
return { count, $reset }
})
变更state
传入对象
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
传入函数
store.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
替换state
- 完全替换掉 store 的 state
- 只能使用$patch
// 这实际上并没有替换`$state`
store.$state = { count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({ count: 24 })
订阅state
类似于 Vuex 的 subscribe 方法,你可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次 (例如,当使用上面的函数版本时)。
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))
})
Getter
基本使用
-
相当于计算属性
-
推荐使用箭头函数,并且它将接收
state作为第一个参数 -
有可能也会不使用自己的state,会使用自己的getter,在一个基础上,计算出另一个
- 通过this 可以访问到整个 store 实例
-
比如
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
// 自动推断出返回类型是一个 number
doubleCount(state) {
return state.count * 2
},
// 返回类型必须明确设置 这是依赖上一个在进行处理
doublePlusOne(): number {
// 整个 store 的 自动补全和类型标注 ✨
return this.doubleCount + 1
},
},
})
组件内部使用getter
<script setup>
import { useCounterStore } from './counterStore'
const store = useCounterStore()
</script>
<template>
<p>Double count is {{ store.doubleCount }}</p>
</template>
向 getter 传递参数
- Getter 只是幕后的计算属性,所以不可以向它们传递任何参数
- 不过,你可以从 getter 返回一个函数,该函数可以接受任意参数
看一下实现
export const useUserListStore = defineStore('userList', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
在组件使用
<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>
访问其他 store 的 getter
- 导入别的仓库
- 直接使用就完事了
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
组件使用方法
<script setup>
const store = useCounterStore()
store.count = 3
store.doubleCount // 6
</script>
Action
action相当于方法
- 通过
defineStore()中的actions属性来定义 - 并且它们也是定义业务逻辑的完美选择
export const useCounterStore = defineStore('main', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})
注意
- action 也可通过
this访问整个 store 实例 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
}
},
},
})
Action 可以像函数或者通常意义上的方法一样被调用
<script setup>
const store = useCounterStore()
// 将 action 作为 store 的方法进行调用
store.randomizeCounter()
</script>
<template>
<!-- 即使在模板中也可以 -->
<button @click="store.randomizeCounter()">Randomize</button>
</template>
访问其他 store 的 action
- 也是导入 使用就可以
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
preferences: null,
// ...
}),
actions: {
async fetchUserPreferences() {
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
以上就是本次文章的全部内容,如有帮助,记得点赞收藏,感谢您的观看。