Pinia 是 Vue3 的存储库,代替 Vuex 成为VUE3的状态管理工具。相比于 Vuex 它有以下优势:
- 不存在mutations,存储数据的方式更加简化。
- 在组件上可以直接做存储库中的数据的修改,并且都是响应式的。
- 贴合ts。
yarn add pinia 或者 npm install pinia
stores/store.ts
文件,使用如下代码初始化pinia仓库并在main.ts中启用piniapinia常用的模块有三个:
import { defineStore } from "pinia";
const useUserStore = defineStore({
// 当前存储库的唯一键
id: "user",
// 存储库,他必须以一个函数的形式存在,在函数里面返回具体要存储的数据或者对象
state: () => ({
name: "Jerry",
age: 20,
userInfo: "",
}),
// 相当于计算属性 computed
getters: {
userInfo: (state) => "姓名:" + state.name + "--年龄:" + state.age,
},
// 可以处理异步或者同步方法逻辑
actions: {},
});
const useGoodsStore = defineStore({
// 当前存储库的唯一键
id: "goods",
// 存储库,他必须以一个函数的形式存在,在函数里面返回具体要存储的数据或者对象
state: () => ({
name: "衣服",
price: 100,
goodsInfo: "",
}),
// 相当于计算属性 computed
getters: {
goodsInfo: (state) => "名称:" + state.name + "--价格:" + state.price,
},
// 可以处理异步或者同步方法逻辑
actions: {},
});
export { useGoodsStore, useUserStore };
// 在main.ts中启用pinia
import { createPinia } from 'pinia'
app.use(createPinia())
$patch
使用对象改变值$patch
使用函数改变值,使用函数的好处就是能增减逻辑判断goodsStore.$state
直接改变值,但是要写全定义的所有的变量actions
中定义的方法:goodsStore.setGoodName()
<script setup lang="ts">
import { useGoodsStore, useUserStore } from './stores/store'
import { ref } from 'vue'
// 定义两个v-mode的变量,用于接受输入值
const goodsName = ref<string>('')
const userName = ref<string>('')
// 调用store里面定义的两个钩子函数,初始化仓库
const userStore = useUserStore()
const goodsStore = useGoodsStore()
const changeGoodsInfo = () => {
console.log('changeGoodsInfo')
// goodsStore.name = goodsName.value // a、直接赋值
// goodsStore.$patch({ // b、使用`$patch`使用对象改变值
// name: goodsName.value
// })
// goodsStore.$patch((store) => { // c、 使用`$patch`使用函数改变值,使用函数的好处就是能增减逻辑判断
// if (goodsName.value != '') {
// store.name = goodsName.value
// }
// })
// goodsStore.$state = { // d、直接使用`goodsStore.$state`直接改变值,但是要写全定义的所有的变量
// name: goodsName.value,
// price: 1.2,
// goodsInfo: '姓名:' + goodsName.value + '--年龄:' + 1.2
// }
goodsStore.setGoodName() // e、直接调用`actions`中定义的方法:`goodsStore.setGoodName()`
}
const changeUserInfo = () => {
console.log('changed')
userStore.name = userName.value
}
</script>
<template>
<div>
<p>用户信息:{{ userStore.userInfo }}</p>
<p>商品信息:{{ goodsStore.goodsInfo }}</p>
<br />
<hr />
姓名:<input type="text" v-model="userName" />
<button @click="changeUserInfo">提交用户信息</button>
<br />
<hr />
名称:<input type="text" v-model="goodsName" />
<button @click="changeGoodsInfo">提交商品信息</button>
</div>
</template>
<style scoped></style>
pinia
提供的storeToRefs
进行转化import { storeToRefs } from 'pinia'
const goodsStore = useGoodsStore()
// let { name, price } = goodsStore
let { name, price } = storeToRefs(goodsStore)
const changeGoodsInfo = () => {
console.log('changeGoodsInfo', name, price)
price.value += 1
}
// storeToRefs的源码解析
function storeToRefs(store) {
// 判断当前是Vue2的直接使用toRefs进行包裹处理
if (isVue2) {
return toRefs(store);
}
else {
// 拿到store的原始对象,实际上处理之后就是一个Object对象
store = toRaw(store);
// 最终返回的就是refs集合,下边如果发现store中的ref或者reactive对象之后,就会被放到refs集合中
const refs = {};
for (const key in store) {
const value = store[key];
if (isRef(value) || isReactive(value)) {
// 将store中的ref或者reactive对象放到refs集合中
refs[key] =
// ---
toRef(store, key);
}
}
return refs;
}
}
// 相当于计算属性 computed:getters中的方法当state中的任何一个属性变化时,就会给监听到
getters: {
goodsInfo: state => {
console.log('goodsInfo', '=====')
return '名称:' + state.name + '--价格:' + state.price
},
getGoodsInfo(): string {
console.log('getGoodsInfo', '=====')
return '名称:' + this.name + '--价格:' + this.getGoodsPrice + '元' // 这里使用箭头函数的话,this指向就会出现问题,无法使用this调用getGoodsPrice
},
getGoodsPrice: state => {
console.log('getGoodsPrice', '=====')
state.price *= 100
return state.price
}
},
// 定义Goods类型
type Goods = {
name: string;
price: number;
};
// 模拟一个异步方法:3秒后返回数据
const setGoodsInfo = (): Promise<Goods> => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: "iPad Pro",
price: 29.96,
});
}, 3000);
});
};
// 可以处理异步或者同步方法逻辑
actions: {
setGoodsName() {
this.name = 'Zara'
this.setGoodsPrice() // 普通防范中调用普通方法
},
setGoodsPrice() {
this.price = 200
},
// 在actions中可以使用异步和同步方法,
async getGoodsInfo1() {
const result = await setGoodsInfo() // 同步调用setGoodsInfo获取数据,3秒后返回数据
this.name = result.name
this.price = result.price
this.setGoodsName() // 异步方法中调用普通方法
}
},
// goodsStore.$patch({ // b、使用`$patch`使用对象改变值
// name: goodsName.value
// })
// goodsStore.$patch((store) => { // c、 使用`$patch`使用函数改变值,使用函数的好处就是能增减逻辑判断
// if (goodsName.value != '') {
// store.name = goodsName.value
// }
// })
// goodsStore.$state = { // d、直接使用`goodsStore.$state`直接改变值,但是要写全定义的所有的变量
// name: goodsName.value,
// price: 1.2,
// goodsInfo: '姓名:' + goodsName.value + '--年龄:' + 1.2
// }
const changeGoodsInfo = () => {
console.log("changeGoodsInfo", name, price);
goodsStore.getGoodsInfo1();
setTimeout(() => {
goodsStore.$reset(); // 还原成最近一次改变的值
}, 5000);
};
/**
* 当state中的值任意一个发生变化的时候,就会触发该函数
*
* args: 里面会记录新旧值
* state:就是当前操作的state的实例
* options: 是一个对象,比如detached,这是一个boolean参数,当这个参数为true时,表明即使当前组件销毁后,也继续监控state里面值的变化,可选
*/
goodsStore.$subscribe((args, state) => {
console.log('args', args)
console.log('state', state)
},{
detached: true
})
/**
* 当调用actions里面的函数的时候,就会触发改函数
*
* args:接收参数,里面封装了多个api:
* args.after:当$onAction里面的逻辑执行完成之后才会执行args.after函数逻辑,所以args.after放置的位置于执行顺序无关
* args.onError:当调用actions里面的函数发生错误时,args.onError函数也会执行
* args.args:接收调用actions里面的函数传递的参数,是一个数组
* args.name:执行的actions里面的函数的名称
* detached: 这是一个boolean参数,当这个参数为true时,表明即使当前组件销毁时,也继续监控actions里面的函数调用,可选
*/
goodsStore.$onAction((args) => {
args.after(() => console.log("args.after", "===="));
console.log("args", args);
}, true);
当我们刷新页面或者跳转到下一个组件的时候,组件中展示的pinia中的值会被丢失,即还原成默认值,所以要实现将pinia中更改过的值持久化,这里采用localstore。
import { PiniaPluginContext } from "pinia";
import { toRaw } from "vue";
/**
* 存储localStorage
*
* @param key 给定的key
* @param value 给定的值
*/
const setLocalStorage = (key: string, value: string) => {
try {
localStorage.setItem(key, value);
} catch (err) {
console.log(err);
}
};
/**
* 获取localStorage
*
* @param key 给定的key
* @returns 返回对应的key的值
*/
const getLocalFromStorage = (key: string) => {
try {
return localStorage.getItem(key)
? JSON.parse(localStorage.getItem(key) as string)
: {};
} catch (err) {
console.log(err);
}
return undefined;
};
// 定义一个数据类型
type Options = {
plugin?: string;
key?: string;
};
/**
* pinia插件定义,函数里面包裹函数,主要是为了实现用户自定义参数,外层函数,主要在交给store注册的时候,
* 可以由用户自定参数,而真正被store调用执行的参数是return出去的函数,该函数才会接收store返回的context,里面有state的所有信息
*
* @param optons 用户自定的参数
* @returns 从localStorage存储的state中值
*/
const PiniaPersistencePluggin = (optons: Options) => {
return (context: PiniaPluginContext) => {
const key = optons.key ?? "__PINIA__";
const plugin = optons.plugin ?? "localStorage";
const { store } = context;
const data = getLocalFromStorage(key + `${store.$id}`);
console.log("data===get", data);
store.$subscribe(() => {
if (plugin && plugin == "localStorage") {
setLocalStorage(
key + `${store.$id}`,
JSON.stringify(toRaw(store.$state))
);
}
});
return { ...data };
};
};
// 向外暴漏插件
export { PiniaPersistencePluggin };
// mian.ts中注册使用插件
import { PiniaPersistencePluggin } from './stores/piniaplugin'
const store = createPinia()
store.use(PiniaPersistencePluggin({ plugin: 'localStorage', key: '__PINIA__' })) // 在注册使用插件的时候,可以指定state的数据存储在localStorage获取cookie获取其他,还至指定默认的key
// 在vite.config.ts中加入如下代码,可解决dev过程时的跨域问题,实际生产环境下,用的是ngigx
server: {
port: 8082,
/**
* 这里设置的是是否启用网络地址,如果为false,启动后就不会出现ip的访问地址:
* ➜ Local: http://localhost:8082/
* ➜ Network: http://192.168.217.240:8082/ // 如果该参数为false,就不会出现改地址
*/
host: true,
/**
* 为开发服务器配置CORS。设置为true以允许来自任何源的所有方法,或者使用一个对象单独配置。
*/
cors: true,
// 是否使用https进行访问
https: true,
// 跨域设置
proxy: {
'/api': { // 设置前端在访问后台地址的时候,要以/api开始
target: 'http://localhost:8989', // 访问的目标地址
changeOrigin: true, // 允许将源地址更改为目标地址
rewrite: (path: string) => path.replace(/^\/api/, '') // 将前端请求时候加的/api替换为'',应为/api是前端为了解决跨域问题而加的,真正的后台地址是上是没有这个的
},
}
},