安装
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 (选项式) 建议这种方式
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0, name: 'Eduardo' }),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
定义store (setup)
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('Eduardo')
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, name, doubleCount, increment }
})
In Setup Stores:
• ref()s become state properties
• computed()s become getters
• function()s become actions
在组件中使用store
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'
export default defineComponent({
setup() {
const store = useCounterStore()
// `name` and `doubleCount` 是响应式数据
// 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)
// the increment action can be just extracted
const { increment } = store
return {
name,
doubleCount,
increment,
}
},
})
实践:console.log了下store对象,发现在state和getter定义的变量都会转化成ref响应式数据
在组件中使用(setup方法中),会自动解包,结合结构解析,可以使用storeToRefs方法来保持数据的响应性
定义state (typescript方式)
//建议这种方式
interface State {
userList: UserInfo[]
user: UserInfo | null
}
export const useUserStore = defineStore('user', {
state: (): State => {
return {
userList: [],
user: null,
}
},
})
interface UserInfo {
name: string
age: number
}
//重置state
const store = useStore()
store.$reset()
//状态提交
store.$patch({
count: store.count + 1,
age: 120,
name: 'DIO',
})
//$patch接受一个函数
cartStore.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
其实状态运用响应式数据的特性修改,更为方便一些,特殊情况下也可以使用$patch。
订阅状态
当数据变更时(直接修改state、通过patch函数修改),会触发该回调。
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))
})
// this subscription will be kept even after the component is unmounted
cartStore.$subscribe(callback, { detached: true })
整个状态的检测
watch(
pinia.state,
(state) => {
// persist the whole state to the local storage whenever it changes
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
不建议这么用,状态过多时,开销太大。
定义getter
import { useOtherStore } from './other-store'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
// automatically infers the return type as a number
doubleCount(state) {
return state.count * 2
},
// the return type **must** be explicitly set
doublePlusOne(): number {
// autocompletion and typings for the whole store 这里可以使用this,但要注意如果是typescript需要定义返回值类型
return this.doubleCount + 1
},
otherGetter(state) {
const otherStore = useOtherStore() //使用其他store 引入后 直接使用即可
return state.localData + otherStore.data
},
},
})
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不会缓存,类似于方法调用
选项式 setup中使用
export default {
setup() {
const counterStore = useCounterStore()
return { counterStore }
},
computed: {
quadrupleCounter() {
return this.counterStore.doubleCount * 2
},
},
}
actions使用
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)
// let the form component display the error
return error
}
},
},
})
action方法的调用
export default defineComponent({
setup() {
const store = useCounterStore()
// call the action as a method of the store
store.randomizeCounter()
return {}
},
})
访问其他store actions,同getter
在选项式api中调用,同getter
订阅actions
const unsubscribe = someStore.$onAction(
({
name, // name of the action
store, // store instance, same as `someStore`
args, // array of parameters passed to the action
after, // hook after the action returns or resolves
onError, // hook if the action throws or rejects
}) => {
// a shared variable for this specific action call
const startTime = Date.now()
// this will trigger before an action on `store` is executed
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// this will trigger if the action succeeds and after it has fully run.
// it waits for any returned promised
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// this will trigger if the action throws or returns a promise that rejects
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// manually remove the listener
unsubscribe()
export default {
setup() {
const someStore = useSomeStore()
// this subscription will be kept even after the component is unmounted
someStore.$onAction(callback, true)
// ...
},
}
外部使用,比如vue-router
import { createRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
const router = createRouter({
// ...
})
router.beforeEach((to) => {
const store = useStore()
if (to.meta.requiresAuth && !store.isLoggedIn) return '/login'
})
总结:
整体思路跟vuex是有区别的,pinia完全是小模块的按需引用,不像vuex会有store一个入口文件,会在初始化时将相关内容初始化完毕。而且在使用中访问其他store的getter、action更加方便,结合vue3的响应性,不依赖于commit(patch)的提交,直接修改即可,更加方便。
待学习内容:Plugins的使用
参考文档:https://pinia.vuejs.org/introduction.html