一、Pinia 是什么?
Pinia 最初是在 2019 年 11 月左右重新设计使用 Composition API 。从那时起,最初的原则仍然相同,但 Pinia对 Vue 2 和 Vue 3 都有效,并且不需要您使用组合 API。Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。实际上就是 Vue3 中的 Vuex,它是 Vuex 的升级版,尤雨溪用了都说好。
二、Pinia 的优势
- 它能让你像定义组件一样的去定义 Store,API 旨在让您编写组织良好的 Store 。
- 有着比 Vuex 更好的的 TypeScript 支持。
- 与 Vue devtools 挂钩,为您提供良好的 Vue 2 和 Vue 3 开发体验,即使是Vue 2 的老项目也能使用。
- 体积极小,只有1kb,让您甚至忘记它的存在。
- 模块化设计支持多个 Store ,每一个都是独立诞生,但最终都是相互联系。
- 可以通过安装插件来拓展自身的功能。
- 摒弃了 Mutations 的操作,并且 Actions 支持了同步和异步,简化了状态管理库的使用。
- 支持服务端渲染。
- 更多更详细参考 Pinia官方文档 或者 Pinia中文文档 (中文文档部分翻译难以理解,建议阅读英文文档)
三、Pinia 的安装
- 首先咱们先用 Vite 创建一个 Vue3 的项目,使用Vite创建一个 Vue3项目
- 然后安装 Pinia :
# 选择一个你喜欢的包管理器
# NPM
$ npm install pinia --save
# Yarn
$ yarn add pinia
# pnpm
$ pnpm install pinia
- 项目中引入 Pinia
非常简单有手就行,在 main.ts 文件中引入就行了
// # main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import './style.css'
import App from './App.vue'
const app = createApp(App);
const pinia = createPinia(); // 创建Pinia实例
// 将Pinia实例挂载到Vue根实例上
app.use(pinia)
app.mount('#app')
四、Pinia 的使用
首先在 src 文件夹下新建一个 stores 文件夹,在stores文件夹下再新建一个 index.ts 文件,跟使用 Vuex时的结构一样,然后编写 index.ts 里的代码
- 先引入 Pinia
import { defineStore} from 'pinia'
- 导出
export const useStore = defineStore('store',{
state:() => {
return {}
},
getters:{},
actions:{}
})
defineStore() 方法可以接收两个参数,第一个参数类似于唯一值的id,给我们的容器取一个名字,第二参数是一个配置对象,里面有三个属性 state 、getters 和 actions
在 Vuex 中 state 是一个对象,但在 Pinia 中 state 必须是个箭头函数 return 一个对象,用来存储全局的状态数据
getters 类似于计算属性,有缓存的功能,可以帮助我们修士一些值
actions 类似于 methods,根据不同的需求和业务可以做一些同步或者异步的操作,用来修改全局的状态数据
- 定义一个 state
在 state 里面定义一个全局数据,在项目中的每一个页面或组件都可以通过 Pinia 方法获取到这个数据
state:() => {
return {
name: '海马',
}
},
- 在页面中读取 Pinia 的数据
我们去 HelloWorld 组件中引入 useStore ,然后得到 useStore 的实例在页面中直接使用
- 操作 Pinia 里的数据
修改 Pinia 里的数据有四种方法,根据自己实际使用场景选择不同方法
一、在页面或者组件中直接修改 State(不推荐)
先在 state 中新增一个 age 属性
state:() => {
return {
name: '海马',
age: 18,
}
},
然后在页面中去操作它
<script setup lang="ts">
import { useStore } from '../stores/index'
defineProps<{ msg: string }>()
const store = useStore();
const handleClick = () => {
store.age ++
}
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<p>Hello everybody, my name is {{ store.name }}.</p>
<p>I'm {{ store.age }} years old.</p>
<el-button type="primary" @click="handleClick">happlyBirthday</el-button>
</div>
</template>
可以看到我们在页面上点击按钮的时候 age 也会变化
二、使用 $patch 方法修改 State
使用 $patch 方法时可以直接传入对象去修改 state 的值,也可以传入一个函数,在函数中书写代码逻辑
// 直接上代码
const handleClick = () => {
// 我们可以直接通过 $patch 方式修改 state 的值
// 但是如果要修改复杂数据时需要将整个数据格式都再写一遍,不利于开发效率,不推荐
// let age = store.age;
// age++
// store.$patch({
// name: '青龙',
// age: age
// })
// 还可以传入一个函数
// 这样我们可以在函数中书写逻辑,推荐使用
store.$patch((state) => {
state.age++
if (state.age == 30) state.name = '中年海马'
})
}
三、使用 actions 修改 State (推荐)
我们可以在 store 中的 actions 中定义好函数,在页面实例去调用 actions 中的函数方法就可以了
// stores/index.ts
actions:{
happlyBirthday() {
this.age ++
}
}
// HellowWorld.vue
<script setup lang="ts">
import { useStore } from '../stores/index'
const store = useStore();
const handleClick = () => {
store.happlyBirthday();
}
</script>
<template>
<div>
<p>Hello everybody, my name is {{ store.name }}.</p>
<p>I'm {{ store.age }} years old.</p>
<el-button type="primary" @click="handleClick">happlyBirthday</el-button>
</div>
</template>
注意
请注意,store 是一个用 reactive 包裹的对象,这意味着不需要在 getter 之后写.value,但是,就像 setup 中的 props 一样,我们不能对其进行解构
const { name, age } = store;
<p>Hello everybody, my name is {{ name }}.</p> // 一直是 海马
<p>I'm {{ age }} years old.</p> // 永远 18 岁
为了从 Store 中提取属性同时保持其响应式,官方提供了
storeToRefs()
方法,它将为任何响应式属性创建 refs。 当您仅使用 store 中的状态但不调用任何操作时,这很有用
// 直接引入
import { storeToRefs } from 'pinia'
// 使用
const { name, age } = storeToRefs(store);
四、getters 的使用
Getter 类似于计算属性,相当于 State 的计算值
getters:{
// 类型推断自动推出返回值为 number
nominalAge(state) {
return state.age + 1
},
// 明确定义返回类型
realName(): string {
return `${this.name}·胡`
},
// 还可以互相调用
description(): string {
return `my name is ${this.realName}, ${this.nominalAge} years old`
}
},
// HellowWorld.vue
// 直接在实例上使用
<p>Real name is {{ store.realName }}</p>
<p>Nominal year is {{ store.nominalAge }}</p>
<p>{{ store.description }}</p>
⒅🈲
// HelloWorld.vue
<script setup lang="ts">
import { useStore } from '../stores/index'
import { storeToRefs } from 'pinia'
defineProps<{ msg: string }>()
const store = useStore();
// × 这样会破坏响应式,和从 props 中结构一样
// const { name, age } = store;
const { name, age } = storeToRefs(store);
// 直接修改
const handleClickChange = () => {
store.age ++
}
// 可以通过将其 $state 属性设置为新对象来替换 Store 的整个状态
const handleClickState = () => {
let age = store.age;
age++
store.$state = {
name: '海马',
age,
}
}
// $patch 函数
const handleClickPatchChange = () => {
// 我们可以直接通过 $patch 方式修改 state 的值,但是任何集合修改(例如,从数组中推送、删除、拼接元素)
// 都需要您创建一个新集合,不利于开发效率,不推荐
let age = store.age;
age++
store.$patch({
name: '青龙',
age: age
})
}
const handleClickPatchFn = () => {
// 可以传入一个函数的形式来批量修改集合内部分对象的情况
store.$patch((state) => {
state.age++
if (state.age >= 30) state.name = '中年海马'
})
}
// 在 actions 中定义好函数,页面实例中直接调用
const handleClickAction = () => {
let age = store.age
age ++
store.happlyBirthday(age);
}
// 可以通过调用 store 上的 $reset() 方法将状态 重置 到其初始值
const reset = () => {
store.$reset()
}
</script>
<template>
<h1>{{ msg }}</h1>
<div>
<p>Hello everybody, my name is {{ store.name }}.</p>
<p>I'm {{ store.age }} years old.</p>
<p>Real name is {{ store.realName }}</p>
<p>Nominal year is {{ store.nominalAge }}</p>
<p>{{ store.description }}</p>
<p>Hello everybody, my name is {{ name }}.</p>
<p>I'm {{ age }} years old.</p>
<el-button type="primary" @click="handleClickChange">handleClickChange</el-button>
<p></p>
<el-button type="primary" @click="handleClickState">handleClickState</el-button>
<p></p>
<el-button type="primary" @click="handleClickPatchChange">handleClickPatchChange</el-button>
<p></p>
<el-button type="primary" @click="handleClickPatchFn">handleClickPatchFn</el-button>
<p></p>
<el-button type="primary" @click="handleClickAction">handleClickAction</el-button>
<p></p>
<el-button type="danger" @click="reset">resetData</el-button>
</div>
</template>
// ../stores/index.ts
import { defineStore} from 'pinia'
export const useStore = defineStore('store',{
state:() => {
return {
name: '海马',
age: 18,
}
},
getters:{
// 类型推断自动推出返回值为 number
nominalAge(state) {
return state.age + 1
},
// 明确定义返回类型
realName(): string {
return `${this.name}·胡`
},
// 还可以互相调用
description(): string {
return `my name is ${this.realName}, ${this.nominalAge} years old`
}
},
actions:{
happlyBirthday(age: number) {
this.age = age;
}
}
})
叮,下班啦,打卡成功!