核心概念
定义Store
在深入核心概念之前,我们需要知道store是使用defineStore()定义的,它需要一个唯一的名称,作为第一个参数传递
import { defineStore } from 'pinia'
// useStore 可以是任意的定义,比如useUser、useCart
// 第一个参数必须是一个在应用程序中唯一的名称
export const useStore = defineStore('main', {
// 其它配置项
})
这个名称(也称为id)是必需的,Pinia使用它来连接store到devtools。将返回的函数命名为use…是跨可组合的约定,以使其用法符合习惯。
使用store
我们定义了一个store,因为只有在setup()中调用了useStore(),store才会被创建:
import { useStore } from '@/stores/counter'
export default {
setup() {
const store = useStore()
return {
// 可以返回store的整个实例,以便在组件模板中使用
store
}
}
}
你可以定义任意多的store,你应该在不同的文件中定义每个store,以最大限度地利用pinia(比如自动允许你的bundle进行代码分割和TypeScript推断)。
如果您还没有使用setup组件,您仍然可以使用Pinia,需要参考帮助文档。
一旦store被实例化,您就可以直接在store上访问在state、getter和action中定义的任何属性。我们将在下一页中看到这些细节,但自动完成将帮助你。
注意store是一个用reactive包装的对象,这意味着不需要在getter后面写.value,但是,就像setup中的props一样,我们不能对它进行解构:
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)
}
}
})
为了从store中提取属性,同时保持其响应式,您需要使用storetoref()。它将为每个响应式属性创建引用。当您只从store中使用state而不调用任何操作时,这是非常有用的。注意,你可以直接从store中解构动作,因为它们也被绑定到store本身:
import { storeToRefs } from 'pinia'
export default defineComponent({
setup() {
const store = useStore()
// `name` and `doubleCount` are reactive refs
// This will also create refs for properties added by plugins
// but skip any action or non reactive (non ref/reactive) property
const { name, doubleCount } = storeToRefs(store)
const { increment } = store
return {
name,
doubleCount,
increment
}
}
})
State
大多数时候,state是Store的中心部分。人们通常从定义应用程序的state开始。在Pinia 中,state被定义为一个返回初始state的函数。这保证了Pinia在服务器端和客户端都能使用。
import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 建议使用箭头函数进行完整的类型推断
state: () => {
return {
// 这些所以的属性类型将被自动推断出来
counter: 0,
name: 'Eduardo',
isAdmin: true
}
},
})
TIP
如果您使用
Vue 2,您在state中创建的数据应遵循与Vue实例中data相同的规则,即state对象必须是普通的,并且在向其添加新属性时需要调用Vue.set()。另请参阅:
访问state
通常,你可以直接通过store实例来读写state
const store = useStore()
store.counter++
重置state
通过调用$reset()函数,可以将state重置回初始状态
const store = useStore()
store.$reset()
Options API使用
在以下示例,您可以假设创建了以下store:
// stores/counterStore
import { defineStore } from 'pinia'
const useCounterStore = defineStore('counterStore', {
state: () => ({
counter: 0
})
})
使用setup()
虽然Composition API并不适合所有人,但是setup()钩子可以让Pinia更容易在Options API中使用。不需要额外的辅助函数!
import { useCounterStore } from '../stores/counterStore'
export default {
setup() {
const counterStore = useCounterStore()
return { counterStore }
},
computed: {
tripleCounter() {
return this.counterStore.counter * 3
}
}
}
不使用setup()
如果您不使用Composition API,而您使用的是computed, methods,…,则你可以使用mapState()辅助函数将状态属性映射为只读计算属性:
import { mapState } from 'pinia'
import { useCounterStore } from '../stores/counterStore'
export default {
computed: {
// gives access to this.counter inside the component
// same as reading from store.counter
...mapState(useCounterStore, ['counter'])
// same as above but registers it as this.myOwnName
...mapState(useCounterStore, {
myOwnName: 'counter',
// you can also write a function that gets access to the store
double: store => store.counter * 2,
// it can have access to `this` but it won't be typed correctly...
magicValue(store) {
return store.someGetter + this.counter + this.double
},
}),
},
}
可修改的state
如果您希望能够修改这些状态属性(例如,如果您有一个表单),您可以使用mapWritableState()代替。请注意,您不能像mapState()那样传递函数:
import { mapWritableState } from 'pinia'
import { useCounterStore } from '../store/counterStore'
export default {
computed: {
...mapWritableState(useCounterStore, ['counter'])
...mapWritableState(useCounterStore, {
myOwnName: 'counter',
})
}
}
TIP
您不需要
mapWritableState()来处理像数组这样的集合,除非你用cartItems = []来替换整个数组,mapState()仍然允许你在你的集合上调用方法。
改变state
除了直接使用store.counter++来修改store之外,你还可以使用$patch方法,它允许你同时修改多个改变:
store.$patch({
counter: store.counter + 1,
name: 'Abalam',
})
然而,使用这种语法应用某些改变确实很难或代价高昂:任何集合的修改(例如,从数组中添加、删除、修改元素)都需要您创建一个新集合。正因为如此,$patch方法也接受一个函数来对这种难以应用于patch对象的改变进行分组:
cartStore.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
这里的主要区别是$patch()允许您在devtools中将多个改变分组到一个条目中。注意,直接对state和$patch()的更改将呈现在devtools中,并且需要花费些时间(在Vue 3中还没出现)。
更换state
您可以通过将store的$state属性设置一个新对象来替换整个store的状态:
store.$state = { counter: 666, name: 'Paimon' }
你也可以通过pinia实例的state来更换整体的state状态,这通常被用在SSR
pinia.state.value = {}
订阅state
您可以通过store的$subscribe()方法查看状态及其变化,这与Vuex的subscribe方法类似。与常规的watch()相比,使用$subscribe()的优势在于,订阅只会在补丁之后触发一次(例如,当使用上面的函数版本时)。
cartStore.$subscribe((mutation, state) => {
// import { MutationType } from 'pinia'
mutation.type // 'direct' | 'patch object' | 'patch function'
// same as cartStore.$id
mutation.storeId // 'cart'
// only available with mutation.type === 'patch object'
mutation.payload // patch object passed to cartStore.$patch()
// persist the whole state to the local storage whenever it changes
localStorage.setItem('cart', JSON.stringify(state))
})
默认情况下,状态订阅被绑定到添加它们的组件上(如果store在组件的setup()中)。这意味着,当组件被卸载时,它们将被自动删除。如果你想在组件卸载后保留它们,传递{ detached: true } 作为第二个参数来从当前组件中分离状态订阅:
export default {
setup() {
const someStore = useSomeStore()
// 在组件卸载后,将保留订阅
someStore.$subscribe(callback, { detached: true })
// ...
},
}
TIP
可以在
pinia实例上观察完整的状态watch( pinia.state, (state) => { // 当状态变化时,把它保存在本地 localStorage.setItem('piniaState', JSON.stringify(state)) }, { deep: true } )