概念
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。 支持vue2和vue3
优点:
-
Devtools 支持
- 追踪 actions、mutations 的时间线
- 在组件中展示它们所用到的 Store
- 让调试更容易的 Time travel
-
热更新
- 不必重载页面即可修改 Store
- 开发时可保持当前的 State
-
插件:可通过插件扩展 Pinia 功能
-
为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。
-
支持服务端渲染
本文基于vue3+vite+ts+Element plus项目内容 展开从了解到实现一个简单Demo,主要vue3内容,ts内部内容较少,可以忽略不计,不影响整体
Element Plus内的使用是标签带了el- ,没有安装的,去除这个即可
安装
yarn add pinia
# 或者使用 npm
npm install pinia
引入
引入createPinia方法,将pinia挂载到Vue中
main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
Store
store,数据存储仓库,使用defineStore()定义。
可以对 defineStore() 的返回值进行任意命名,但最好使用 store 的名字,同时以 use 开头且以 Store 结尾。(比如 useUserStore,useCartStore,useProductStore)
第一个参数是你的应用中 Store 的唯一 ID,这个id是必须传入的,Pinia 将用它来连接 store 和 devtools。
创建文件:src/stores/counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
// 其他配置
})
defineStore()的第二个参数采用Option对象
使用 store
在views文件夹下创建组件 Counter.vue,并在 App.vue内引入
App.vue
<script setup lang="ts">
import Counter from './views/Counter.vue'
</script>
<template>
<Counter/>
</template>
创建完成后,在组件Counter.vue内使用
src/views/Counter.vue
<script setup>
import { useCounterStore } from '@/stores/counter';
// 可以在组件中的任意位置访问 `store` 变量 ✨
const counterStore = useCounterStore();
console.log("counterStore", counterStore);
</script>
打印结果:
关于store内部属性的含义,可以看官网属性
defineStore的第二个参数(纯介绍,未在实例内)
defineStore()第二个参数可以接受两类值:Setup函数或者Option对象
下面这两种写法二选一即可
Option Store
传入一个带有 state、actions 与 getters 属性的 Option 对象
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。
Setup Store
和Setup函数类似,可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
function increment() {
count.value++
}
return { count, increment }
})
在 Setup Store 中:
ref()就是state属性computed()就是gettersfunction()就是actions
上面是两种方法介绍,下面例子采用 Option Store进行展开。
State
添加State
在前面配置中,counter.ts 内store创建完成,第一个是store 的id,写为 counter ,第二个参数是option对象,数据需要添加到option对象内的state属性
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
countPow: 0
})
})
获取state
获取store内的数据展示到页面
views/Count.vue
<template>
<div>
<p>当前计数:{{ counterStore.count }}</p>
<div>当前数据二次方:{{ counterStore.countPow }}</div>
</div>
</template>
<script setup>
import { useCounterStore } from '@/stores/counter';
const counterStore = useCounterStore();
</script>
获取数据并展示到页面上
修改数据
修改store内state的数据,需要store内数据并进行修改,但是上面的代码在数据量较少的情况下使用比较清晰,但是代码量较大数据较多时,各种store穿插,代码会显得很多
在改数据的同时,解构数据,但是state内的数据解构后,数据响应会失效,所以需要storeToRefs将state内的数据变为响应式的。
用法:
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter';
const counterStore = useCounterStore();
let { count, countPow } = storeToRefs(counterStore)
组件内使用:
<template>
<div>
<p>当前计数:{{ count }}</p>
<div>当前数据二次方:{{ countPow }}</div>
<el-button @click="add" type="primary">增加</el-button>
<el-button @click="sub" type="primary">减少</el-button>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter';
const counterStore = useCounterStore();
let { count, countPow } = storeToRefs(counterStore)
const add = () => {
count.value++
console.log(counterStore)
};
const sub = () => {
count.value--
};
</script>
运行结果:
Getter
Getter 完全等同于 store 的 state 的计算值。计算属性 computed 的使用。通过 this,你可以访问到其他任何 getter。
getters 添加:
counter.ts
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
countPow: 0
}),
getters: {
doubleCount: (state) => {
return state.count * 2;
},
},
})
数据获取
获取当前数据的二倍值,和获取state的方法一致
Count.vue
<div>计算当前数据二倍值:{{ doubleCount }}</div>
let { count, doubleCount, countPow } = storeToRefs(counterStore)
运行结果:
Action
通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。
action 也可通过 this 访问整个 store 实例,并支持完整的类型标注(以及自动补全✨) 。action 可以是异步的。
同步方法
改动一下数据count的加减计算
添加
defineStore()内的 actions 属性
stores/counter.ts
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
countPow: 0
}),
getters: {
doubleCount: (state) => {
return state.count * 2;
},
},
actions: {
increment() {
this.count++
},
decrement() {
this.count--
}
}
})
使用
获取方法,为了简单,我选择解构,不解构可以直接使用counterStore.increment
views/Counter.vue
<template>
<div>
<p>当前计数:{{ count }}</p>
<div>计算当前数据二倍值:{{ doubleCount }}</div>
<div>当前数据二次方:{{ countPow }}</div>
<el-button @click="add" type="primary">增加</el-button>
<el-button @click="sub" type="primary">减少</el-button>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter';
const counterStore = useCounterStore();
// storeToRefs 将 store内state的数据转变为响应式
let { count, doubleCount, countPow } = storeToRefs(counterStore)
let { increment, decrement } = counterStore
const add = () => {
increment();
};
const sub = () => {
decrement();
};
</script>
方法解构不需要 响应式,直接解构即可,效果如下:
异步方法
异步就是请求接口类似的,添加一个模拟异步接口
actions: {
async powCount() {
//两秒后数据 乘于它的次方
await delay(2000)
this.countPow = this.count ** 2
}
}
获取和其他方法一致:
let { increment, decrement, powCount } = counterStore
powCount()
下面是全量代码,异步代码详细内容可在里面查看!
全量示例代码
main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.use(createPinia())
app.mount('#app')
App.vue
<script setup lang="ts">
import Counter from './views/Counter.vue'
</script>
<template>
<Counter/>
</template>
src/views/Counter.vue
<template>
<div>
<p>当前计数:{{ count }}</p>
<div>计算当前数据二倍值:{{ doubleCount }}</div>
<div>当前数据二次方:{{ countPow }}</div>
<el-button @click="add" type="primary">增加</el-button>
<el-button @click="sub" type="primary">减少</el-button>
<el-button @click="pow" type="primary">异步 count的二次方</el-button>
<div style="font-size: 12px;">两秒后增加当前数据的二次方
⚠️:0和1的二次方不变
</div>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter';
const counterStore = useCounterStore();
console.log("counterStore", counterStore);
// storeToRefs 将 store内state的数据转变为响应式
let { count,doubleCount, countPow } = storeToRefs(counterStore)
let { increment, decrement, powCount } = counterStore
const add = () => {
increment();
};
const sub = () => {
decrement();
};
const pow = () => {
powCount()
}
</script>
下面是两种写法具体对比,二选一即可 src/stores/counter.ts
下面这个是optionduix写法:
import { defineStore } from 'pinia'
// 模拟异步接口
const delay = function delay(interval: number) {
typeof interval !== "number" ? interval = 1000 : null;
return new Promise(resolve => {
setTimeout(() => {
resolve("");
}, interval);
});
};
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
countPow: 0
}),
getters: {
doubleCount: (state) => {
return state.count * 2;
},
},
actions: {
increment() {
this.count++
},
decrement() {
this.count--
},
async powCount() {
//两秒后数据 乘于它的次方
await delay(2000)
this.countPow = this.count ** 2
}
}
})
src/stores/counter.ts
下面是setup函数写法
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
const delay = function delay(interval: number) {
typeof interval !== "number" ? interval = 1000 : null;
return new Promise(resolve => {
setTimeout(() => {
resolve("");
}, interval);
});
};
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const countPow = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
function decrement() {
count.value--
}
const powCount = async () => {
//两秒后数据 乘于它的次方
await delay(2000)
countPow.value = count.value ** 2
}
return { count, countPow, doubleCount, increment, decrement, powCount }
})
总结
pinia相对来说,代码简单很多,没有 mutations,action支持同步异步,而且模块定义 在 defineStore()内定好唯一id
感觉整体和mobx还是挺像的,具体就不深究了
有不足的欢迎指出!