目录
(一)介绍
如果使用 Pinia,即使在小型单页应用中,你也可以获得如下功能:
- Devtools 支持
- 追踪 actions、mutations 的时间线
- 在组件中展示它们所用到的 Store
- 让调试更容易的 Time travel
- 热更新
- 不必重载页面即可修改 Store
- 开发时可保持当前的 State
- 插件:可通过插件扩展 Pinia 功能
- 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。
- 支持服务端渲染
Pinia更适合中、小型项目,大型项目还是更推荐使用vuex
(二)在vue3项目中安装pinia
可以直接在创建vue3项目中选择是否使用pinia,这里展示的是手动安装方法。
npm install pinia
创建一个 pinia 实例 (根 store) 并将其传递给应用
在main.js中:
// 引入pinia
import { createPinia } from 'pinia'
// 创建实例对象
const pinia = createPinia()
// 应用
app.use(pinia)
(三)定义Store
Store 是用 defineStore() 定义的,它的第一个参数必须是独一无二的名字,是应用中 Store 的唯一 ID,Pinia 将用它来连接 store 和 devtools。
对于defineStore()的返回值可以任意命名,但建议使用use开头,store结尾,中间写上应用该store模块的名字,如:useNameStore、useAlertsStore等。
defineStore()的第二个参数可接受两类值:Setup 函数或 Option 对象,即后续即将讲到的Option Store和Setup Store。
1.Option Store
与vue2的选项式api风格类似,传入一个带有state、getters、actions属性的对象
import { defineStore } from "pinia"
export const useAgeStore = defineStore('age', {
state: () => ({ // state使用函数形式返回 防止数据发生污染
age: 18
}),
getters: {
doubleAge(state) {
return state.age * 2
}
},
actions: {
addAge() {
// this指针指向store
this.age++
}
}
})
2.Setup Store
与vue3的组合式api的setup()函数类似
import { defineStore } from "pinia"
import { ref } from 'vue'
export const useNameStore = defineStore('name', () => {
// state
let name = ref('csq')
// getter
let doubleName = computed(() => {
return name.value + '*'
})
// actions
const addName = function () {
name.value += '++'
}
// 将需要用到的数据、函数传出去
return {
name, doubleName, addName
}
})
3.使用store
在 Setup Store 中:
- ref() 就是 state 属性
- computed() 就是 getters
- function() 就是 actions
在组件中引入store函数,直接使用
import { useAgeStore } from "@/store/index";
const ageStore = useAgeStore()
<h2>{{ ageStore.age }}</h2>
<h2>{{ ageStore.doubleAge }}</h2>
<button @click="ageStore.addAge()">增加age</button>
如图,defineStore()返回的对象实际是用proxy进行代理的对象,因此state不需要加.value才能获取到state值
store 是一个用 reactive 包装的对象,这意味着不需要在 getters 后面写 .value
结构赋值:
利用es6的解构赋值可以将store里的state、getters、actions提取出来,但是得到的state和getters不是响应式数据。actions可以直接解构出来,因为它们也被绑定到 store 上。
为了从 store 中提取属性时保持其响应性,需要使用 storeToRefs(),在只需要使用到store的state和getters时非常有用
import { useNameStore } from "@/store/index";
import { storeToRefs } from "pinia";
const nameStore = useNameStore()
// const { name, doubleName } = nameStore // 这样解构出的数据不是响应式
// 借助storeToRefs()实现响应式引用
const { name, doubleName } = storeToRefs(nameStore)
const { addName } = nameStore
<h2>{{ name }}</h2>
<h2>{{ doubleName }}</h2>
<button @click="addName()">增加name</button>
(四)State
1.state与ts
const useStore = defineStore('storeId', {
state: () => {
return {
// 用于初始化空列表
userList: [] as UserInfo[],
// 用于尚未加载的数据
user: null as UserInfo | null,
}
},
})
interface UserInfo {
name: string
age: number
}
2.修改state中的状态
方式1:直接修改 (不建议)
const personStore = usePersonStore()
const changeInfo = function () {
// 方式1:直接修改 (不建议)
personStore.name = 'zkj'
personStore.age = 20
}
方式2:$patch(对象) 实现批量修改 (建议)
const personStore = usePersonStore()
const changeInfo = function () {
// 方式2:$patch(对象) 实现批量修改 (建议)
personStore.$patch({
name: 'zkj',
age: 20,
arr: [...personStore.arr, 5] // 过于繁琐,且对数组进行其他操作不方便
})
}
方式3:$patch(函数) 实现批量修改 (强烈建议)
const personStore = usePersonStore()
const changeInfo = function () {
// 方式3 $patch(函数) 实现批量修改 (强烈建议)
personStore.$patch((state) => { // 形参state 可获取store里的state
state.name = 'zkj'
state.age = 20
state.arr.splice(2, 1)
})
}
方式4:封装到actions函数中 (逻辑复杂时使用)
// 方式4:封装到actions函数中 (逻辑复杂时使用)
personStore.changeInfo()
(五)Getter
1.基本使用
Getter 完全等同于 store 的 state 的计算值。可以通过
defineStore()
中的getters
属性来定义它们。推荐使用箭头函数,并且它将接收state
作为第一个参数
export const usePersonStore = defineStore('person', {
state: () => ({
name: 'csq',
age: 18
}),
getters: {
// 箭头函数 第一个参数接收state(推荐写法)
getterName: (state) => {
return state.name + '*'
},
}
<h2>{{ personStore.getterName }}</h2>
使用this指针获取store
注意:使用箭头函数会导致this指针无法指向store
getters:{
// this指针指向store 但无法进行类型推断(ts),必须手动指明返回值的数据类型
getterAge(): number {
return this.age * 2
},
}
<h2>{{ personStore.getterName }}</h2>
2.访问其他getter
getters:{
// 利用this指针和state可以做到getters数据之间的交互
getterAll(state): string {
return state.name + this.getterAge
},
}
<h2>{{ personStore.getterAll }}</h2>
3.向getter传递参数
getters:{
// 返回函数 可以实现想getters传递参数
// getter 将不再被缓存,它们只是一个被你调用的普通函数
getterAge2: (state) => {
return (data: number) => data + state.age
},
}
<h2>{{ personStore.getterAge2(100) }}</h2>
4.访问其他store的getter
import { useCounterStore } from "./useCounterStore"
getters:{
// 访问其他store的getter
getterOther: (state) => {
const counterStore = useCounterStore()
return state.age + counterStore.count
}
}
<h2>{{ personStore.getterOther }}</h2>
(六)Action
Action 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。
action 可通过 this 访问整个 store 实例,不同的是,action 可以是异步的,你可以在它们里面 await 调用任何 API,以及其他 action 。
1.异步请求
setup store写法:
export const usePersonStore = defineStore('person', () => {
let name = ref('zkj')
let age = ref(18)
// 异步获取person数据
const getData = async function () {
let res = await axios.get('http://localhost:5000/student')
console.log(res);
name.value = res.data.name // name是ref函数定义的数据
age.value = res.data.age
}
return {
name, age, getData
}
})
option store写法:
export const usePersonStore = defineStore('person', {
state: () => ({
name: 'zkj',
age: 18
}),
actions: {
// 此时this指针指向store实例 只能使用普通函数,不能使用箭头函数!!
async getData() {
let res = await axios.get('http://localhost:5000/student')
console.log(res);
this.name = res.data.name
this.age = res.data.name
}
}
})
2.访问其他store的action
setup store写法:
// 访问其他store的action
const getData = async function () {
const counterStore = useCounterStore()
if (counterStore.login()) { // 如果为真 执行数据请求命令
let res = await axios.get('http://localhost:5000/student')
console.log(res);
name.value = res.data.name // name是ref函数定义的数据
age.value = res.data.age
}
}
(七)pinia和vuex的区别
- 与 Vuex 相比,Pinia 不仅提供了一个更简单的 API,也提供了符合组合式 API 风格的 API,最重要的是,搭配 TypeScript 一起使用时有非常可靠的类型推断支持。
- pinia 没有 mutations,而actions的使用不同,在actions中可以处理同步也可以处理异步,getters的使用是一致的
- pinia 没有总出口全是模块化,需要定义模块名称,当多个模块需要协作的时候需要引入多个模块,vuex是有总入口的,在使用模块化的时候不需要引入多个模块
- pinia 在修改状态的时候不需要通过其他api,vuex需要通过commit,dispatch去修改所以在语法上比vuex更容易理解和使用,灵活
pinia就是更好的vuex,在项目中可以直接使用它了,尤其是使用了TypeScript的项目。