Store
export default defineComponent({
setup() {
const store = useStore()
// ❌ 这不起作用,因为它会破坏响应式
// 这和从 props 解构是一样的
const { name, doubleCount } = store
name // "eduardo"
doubleCount // 2
return {
// 一直会是 "eduardo"
name,
// 一直会是 2
doubleCount,
// 这将是响应式的
doubleValue: computed(() => store.doubleCount),
}
},
})
storeToRefs解构响应式
import { storeToRefs } from 'pinia'
export default defineComponent({
setup() {
const store = useStore()
// `name` 和 `doubleCount` 是响应式引用
// 这也会为插件添加的属性创建引用
// 但跳过任何 action 或 非响应式(不是 ref/reactive)的属性
const { name, doubleCount } = storeToRefs(store)
return {
name,
doubleCount
}
},
})
State
定义
import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
counter: 0,
name: 'Eduardo',
isAdmin: true,
}
},
})
访问
默认情况下,您可以通过 store 实例访问状态来直接读取和写入状态:
const store = useStore()
store.counter++
$reset重置到其初始值
通过调用 store 上的 $reset() 方法将状态 重置 到其初始值:
const store = useStore()
store.$reset()
使用选项 API(箭头函数创建)
/ Example File Path:
// ./src/stores/counterStore.js
import { defineStore } from 'pinia',
const useCounterStore = defineStore('counterStore', {
state: () => ({
counter: 0
})
})
使用 setup()
setup() 钩子可以使在 Options API 中使用 Pinia 更容易。 不需要额外的 map helper!
import { useCounterStore } from '../stores/counterStore'
export default {
setup() {
const counterStore = useCounterStore()
return { counterStore }
},
computed: {
tripleCounter() {
return counterStore.counter * 3
},
},
}
改变状态(最好统一用$patch 方便维护)
除了直接用 store.counter++
修改 store
,你还可以调用 $patch
方法。 它允许您使用部分“state
”对象同时应用多个更改:
store.$patch({
counter: store.counter + 1,
name: 'Abalam',
})
但是,使用这种语法应用某些突变非常困难或代价高昂:任何集合修改(例如,从数组中推送、删除、拼接元素)都需要您创建一个新集合。 正因为如此,$patch
方法也接受一个函数来批量修改集合内部分对象的情况:
cartStore.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
这里的主要区别是$patch()
允许您将批量更改的日志写入开发工具中的一个条目中。 注意两者,state
和 $patch()
的直接更改都出现在 devtools
中,并且可以进行 time travelled
(在 Vue 3 中还没有)。
替换state
您可以通过将其 $state
属性设置为新对象来替换 Store
的整个状态:
store.$state = { counter: 666, name: 'Paimon' }
您还可以通过更改 pinia
实例的 state
来替换应用程序的整个状态。 这在 SSR for hydration 期间使用。
pinia.state.value = {}
订阅状态
可以通过 store
的 $subscribe()
方法查看状态及其变化,类似于 Vuex
的 subscribe
方法。 与常规的 watch()
相比,使用 $subscribe()
的优点是 subscriptions
只会在 patches
之后触发一次(例如,当使用上面的函数版本时)。
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 // 补丁对象传递给 to cartStore.$patch()
// 每当它发生变化时,将整个状态持久化到本地存储
localStorage.setItem('cart', JSON.stringify(state))
})
默认情况下,state subscriptions
绑定到添加它们的组件(如果 store
位于组件的 setup()
中)。 意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 { detached: true }
作为第二个参数传递给 detach
当前组件的 state subscription:
export default {
setup() {
const someStore = useSomeStore()
// 此订阅将在组件卸载后保留
someStore.$subscribe(callback, { detached: true })
// ...
},
}
注意
您可以在 pinia 实例上查看整个状态:
watch(
pinia.state,
(state) => {
// 每当它发生变化时,将整个状态持久化到本地存储
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
Getters
用 defineStore()
中的 getters
属性定义。 接收“状态”作为第一个参数以鼓励箭头函数的使用:
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
doubleCount: (state) => state.counter * 2,
},
})
大多数时候,getter
只会依赖状态,但是,他们可能需要使用其他 getter
。 正因为如此,我们可以在定义常规函数时通过 this
访问到 整个 store
的实例, 但是需要定义返回类型(在 TypeScript
中)。 这是由于 TypeScript
中的一个已知限制,并且不会影响使用箭头函数定义的 getter
,也不会影响不使用 this
的 getter
:
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
// 自动将返回类型推断为数字
doubleCount(state) {
return state.counter * 2
},
// 返回类型必须明确设置
doublePlusOne(): number {
return this.counter * 2 + 1
},
},
})
然后你可以直接在 store
实例上访问 getter
:
<template>
<p>Double count is {{ store.doubleCount }}</p>
</template>
<script>
export default {
setup() {
const store = useStore()
return { store }
},
}
</script>
访问其他 getter
与计算属性一样,您可以组合多个 getter
。 通过 this
访问任何其他 getter
。
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
// 类型是自动推断的,因为我们没有使用 `this`
doubleCount: (state) => state.counter * 2,
doubleCountPlusOne(): number {
// 自动完成 ✨
return this.doubleCount + 1
},
},
})
将参数传递给 getter
Getters
只是幕后的 computed
属性,因此无法向它们传递任何参数。 但是,您可以从 getter
返回一个函数以接受任何参数:
export const useStore = defineStore('main', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
并在组件中使用:
<script>
export default {
setup() {
const store = useStore()
return { getUserById: store.getUserById }
},
}
</script>
<template>
<p>User 2: {{ getUserById(2) }}</p>
</template>
如果值没有改变,多次使用就会多次调用
请注意,在执行此操作时,getter
不再缓存,它们只是您调用的函数。 但是,您可以在 getter
本身内部缓存一些结果,这并不常见,但应该证明性能更高:
export const useStore = defineStore('main', {
getters: {
getActiveUserById(state) {
const activeUsers = state.users.filter((user) => user.active)
return (userId) => activeUsers.find((user) => user.id === userId)
},
},
})
访问其他 Store 的getter
要使用其他存储 getter
,您可以直接内部使用它:
import { useOtherStore } from './other-store'
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
与 setup() 一起使用
您可以直接访问任何 getter
作为 store
的属性(与 state
属性完全一样):
export default {
setup() {
const store = useStore()
store.counter = 3
store.doubleCount // 6
},
}
Actions
Actions 相当于组件中的 methods。
import { mande } from 'mande'
const api = mande('/api/users')
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
userData: null,
}),
actions: {
increment() {
this.counter++
},
//通过 this 访问 whole store instance并提供完整类型(不像getters还需定义返回类型)
// 完全自由地设置你想要的任何参数并返回任何东西。 调用 Action 时,一切都会自动推断!
randomizeCounter() {
this.counter = Math.round(100 * Math.random())
},
// 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
}
}
},
})
像 methods 一样被调用:
export default defineComponent({
setup() {
const main = useMainStore()
// Actions 像 methods 一样被调用:
main.randomizeCounter()
return {}
},
})
访问其他 store 操作
您可以直接在操作内部使用它:
import { useAuthStore } from './auth-store'
export const useSettingsStore = defineStore('settings', {
state: () => ({
// ...
}),
actions: {
async fetchUserPreferences(preferences) {
// 访问其他 store 操作
const auth = useAuthStore()
if (auth.isAuthenticated) {
this.preferences = await fetchPreferences()
} else {
throw new Error('User must be authenticated')
}
},
},
})
订阅 Actions
可以使用 store.$onAction()
订阅 action
及其结果。 传递给它的回调在 action
之前执行。 after
处理 Promise
并允许您在 action
完成后执行函数。 以类似的方式,onError
允许您在处理中抛出错误。 这些对于在运行时跟踪错误很有用
这是一个在运行 action 之前和它们 resolve/reject 之后记录的示例。
const unsubscribe = someStore.$onAction(
({
name, // action 的名字
store, // store 实例
args, // 调用这个 action 的参数
after, // 在这个 action 执行完毕之后,执行这个函数
onError, // 在这个 action 抛出异常的时候,执行这个函数
}) => {
// 记录开始的时间变量
const startTime = Date.now()
// 这将在 `store` 上的操作执行之前触发
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 如果 action 成功并且完全运行后,after 将触发。
// 它将等待任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回 Promise.reject ,onError 将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动移除订阅
unsubscribe()
手动移除订阅
默认情况下,action subscriptions
绑定到添加它们的组件(如果 store
位于组件的 setup()
内)。 意思是,当组件被卸载时,它们将被自动删除。 如果要在卸载组件后保留它们,请将 true
作为第二个参数传递给当前组件的 detach action subscription:
export default {
setup() {
const someStore = useSomeStore()
// 此订阅将在组件卸载后保留
someStore.$onAction(callback, true)
// ...
},
}