1. Pinia安装:
npm install pinia
// main.ts
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')
createPinia()函数返回的Pinia类型的实例
定义一个store
import { defineStore } from 'pinia'
// 你可以任意命名 `defineStore()` 的返回值,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。
// 第一个参数是你的应用中 Store 的唯一 ID。
export const useAlertsStore = defineStore('alerts', {
// 其他配置...
})
defineStore()
的第二个参数可接受两类值:Setup 函数或 Option 对象。
两种类型的传参,代表两种不同的编程方式:
setup:是组合式API,其中ref,computed,函数代表了state, getter, actions。这种编程方式更灵活,同时,还可以使用provide,useRoute()函数等。
Option: 选项式。更简单清晰
2. store的解构
const { name, doubleCount } = store 这种写法会丢失响应式
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
这种方式解构可以保留响应式,这种方式更类似vue3的toRefs()
3. pinia与TypeScript
interface State {
userList: UserInfo[]
user: UserInfo | null
}
const useStore = defineStore('storeId', {
state: (): State => {
return {
userList: [],
user: null,
}
},
})
interface UserInfo {
name: string
age: number
}
4. Store API:
store.$reset(): 重置为初始值
const store = useStore()
store.$reset()
也可以创建自己的$reset()函数
store.$patch:一次更新多个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
// 这实际上并没有替换`$state`
store.$state = { count: 24 }
// 在它内部调用 `$patch()`:
store.$patch({ count: 24 })
pinia.state.value = {} // 设置整个应用的state的初始值
订阅 state
可以通过 store 的 $subscribe() 方法侦听 state 及其变化。比起普通的 watch(),使用 $subscribe() 的好处是 subscriptions 在 patch 后只触发一次.
其中,最后一句话的意思表达的是当页面有多次patch的情况,$subscribe()的回调函数只会执行一次。
{ detached: true }
作为第二个参数,当组件被卸载时,数据依然保留
<script setup>
const someStore = useSomeStore()
// 此订阅器即便在组件卸载之后仍会被保留
someStore.$subscribe(callback, { detached: true })
</script>
全局监听:(可以通过持久化插件实现)
watch(
pinia.state,
(state) => {
// 每当状态发生变化时,将整个 state 持久化到本地存储。
localStorage.setItem('piniaState', JSON.stringify(state))
},
{ deep: true }
)
Getter
getter类似computed;getter之间可以相互调用(通过this访问);通过store可以直接访问getter
可以给getter传参:
export const useUserListStore = defineStore('userList', {
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId)
},
},
})
// 组件中使用
<p>User 2: {{ getUserById(2) }}</p>
访问其他 store 的 getter
import { useOtherStore } from './other-store' // 引入其他store
export const useStore = defineStore('main', {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore()
return state.localData + otherStore.data
},
},
})
Action
action中定义的函数,可以通过同步和异步的方式来修改state
export const useCounterStore = defineStore('main', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
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 也可通过 this
访问整个 store 实例
订阅 action
store.$onAction()
来监听 action 和它们的结果
你可以通过 store.$onAction()
来监听 action 和它们的结果。传递给它的回调函数会在 action 本身之前执行。after
表示在 promise 解决之后,允许你在 action 解决后执行一个回调函数。同样地,onError
允许你在 action 抛出错误或 reject 时执行一个回调函数。
const unsubscribe = someStore.$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插件:
import { createPinia } from 'pinia'
// 创建的每个 store 中都会添加一个名为 `secret` 的属性。
// 在安装此插件后,插件可以保存在不同的文件中
function SecretPiniaPlugin() {
return { secret: 'the cake is a lie' }
}
const pinia = createPinia()
// 将该插件交给 Pinia
pinia.use(SecretPiniaPlugin)
// 在另一个文件中
const store = useStore()
store.secret // 'the cake is a lie'
可以通过插件的形式添加全局路由
插件函数:
export function myPiniaPlugin(context) {
context.pinia // 用 `createPinia()` 创建的 pinia。
context.app // 用 `createApp()` 创建的当前应用(仅 Vue 3)。
context.store // 该插件想扩展的 store
context.options // 定义传给 `defineStore()` 的 store 的可选对象。
// ...
}
可选参数context,通过context可以获取app
扩展 Store
pinia.use(() => ({ hello: 'world' }))
//通过在一个插件中返回包含特定属性的对象来为每个 store 都添加上特定属性
//可以直接在 store 上设置该属性,但可以的话,请使用返回对象的方法,这样它们就能被 devtools 自动追踪到:
pinia.use(({ store }) => {
store.hello = 'world'
})
通过函数方式可以在devtools上追踪到
如果想在dev模式种devtools上排查:
// 上文示例
pinia.use(({ store }) => {
store.hello = 'world'
// 确保你的构建工具能处理这个问题,webpack 和 vite 在默认情况下应该能处理。
if (process.env.NODE_ENV === 'development') {
// 添加你在 store 中设置的键值
store._customProperties.add('hello')
}
})
const sharedRef = ref('shared')
pinia.use(({ store }) => {
// 每个 store 都有单独的 `hello` 属性
store.hello = ref('secret')
// 它会被自动解包
store.hello // 'secret'
// 所有的 store 都在共享 `shared` 属性的值
store.shared = sharedRef
store.shared // 'shared'
})
store.shared 可以获取值,每个 store 都被 reactive包装过,所以可以自动解包任何它所包含的 Ref(ref()
、computed()
...)
在回掉函数里定义的响应式对象,是每个store私有的
在回掉函数外调用的,可以在所有store中共享
添加新的外部属性
当添加外部属性、第三方库的类实例或非响应式的简单值时,你应该先用 markRaw()
来包装一下它,再将它传给 pinia。markRaw()
是 Vue 3 中的一个实用函数,用于标记一个对象使其不再被 Vue 的响应系统追踪。markRaw()
可以使一个对象失去响应性
import { markRaw } from 'vue'
// 根据你的路由器的位置来调整
import { router } from './router'
pinia.use(({ store }) => {
store.router = markRaw(router)
})
这里路由并不需要响应式
在插件中使用订阅:
pinia.use(({ store }) => {
store.$subscribe(() => {
// 响应 store 变化
})
store.$onAction(() => {
// 响应 store actions
})
})
防抖选项:
defineStore('search', {
actions: {
searchContacts() {
// ...
},
},
// 这将在后面被一个插件读取
debounce: {
// 让 action searchContacts 防抖 300ms
searchContacts: 300,
},
})
在插件中使用防抖:
// 使用任意防抖库
import debounce from 'lodash/debounce'
pinia.use(({ options, store }) => {
if (options.debounce) {
// 我们正在用新的 action 来覆盖这些 action
return Object.keys(options.debounce).reduce((debouncedActions, action) => {
debouncedActions[action] = debounce(
store[action],
options.debounce[action]
)
return debouncedActions
}, {})
}
})
Pinia类型编程:
import { PiniaPluginContext } from 'pinia'
export function myPiniaPlugin(context: PiniaPluginContext) {
// ...
}
import 'pinia'
declare module 'pinia' {
export interface PiniaCustomProperties {
// 通过使用一个 setter,我们可以允许字符串和引用。
set hello(value: string | Ref<string>)
get hello(): string
// 你也可以定义更简单的值
simpleNumber: number
// 添加路由(#adding-new-external-properties)
router: Router
}
}