Loading

Vue3 + vite + Ts + pinia + 实战

Pinia 起始于 2019 年 11 月左右的一次实验,其目的是设计一个拥有组合式 API 的 Vue 状态管理库。从那时起,我们就倾向于同时支持 Vue 2 和 Vue 3,并且不强制要求开发者使用组合式 API,我们的初心至今没有改变。除了安装和 SSR 两章之外,其余章节中提到的 API 均支持 Vue 2 和 Vue 3。虽然本文档主要是面向 Vue 3 的用户,但在必要时会标注出 Vue 2 的内容,因此 Vue 2 和 Vue 3 的用户都可以阅读本文档。

完整的 ts 的支持;

足够轻量,压缩后的体积只有1kb左右;去除 mutations,只有 state,getters,actions;actions 支持同步异步代码扁平化没有模块嵌套,只有 store 的概念,store 之间可以自由使用,每一个store都是独立的无需手动添加 store,store 一旦创建便会自动添加;支持Vue3 和 Vue2

为什么要使用 Pinia:

Pinia 是 Vue 的存储库,它允许跨组件/页面共享状态

如果您熟悉 Composition API,您可能会认为您已经可以通过一个简单的 export const state = reactive({}). 这对于单页应用程序来说是正确的,但如果它是服务器端呈现的,会使您的应用程序暴露于安全漏洞。 但即使在小型单页应用程序中,您也可以从使用 Pinia 中获得很多好处:

dev-tools 支持 跟踪动作、突变的时间线 Store 出现在使用它们的组件中 time travel 和 更容易的调试 热模块更换 在不重新加载页面的情况下修改您的 Store 在开发时保持任何现有状态 插件:使用插件扩展 Pinia 功能 为 JS 用户提供适当的 TypeScript 支持或 autocompletion 服务器端渲染支持

官方文档Pinia[1]

git 地址 [2]

Pinia 安装和引入

起步 安装

  •  
  •  
  •  
yarn add pinia
npm install pinia

引入注册Vue3

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import { createApp } from 'vue'import App from './App.vue'import {createPinia} from 'pinia'
const store = createPinia()let app = createApp(App) 
app.use(store)
app.mount('#app')

Vue2 使用

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)const pinia = createPinia()
new Vue({  el: '#app',  // other options...  // ...  // note the same `pinia` instance can be used across multiple Vue apps on  // the same page  pinia,})

Pinia 初始化仓库Store

1.新建一个文件夹Store 2.新建文件[name].ts
3.定义仓库Store

  •  
import { defineStore } from 'pinia'

4.我们需要知道存储是使用定义的defineStore(),并且它需要一个唯一的名称,作为第一个参数传递

这儿名称抽离出去了, 新建文件store-namespace/index.ts

  •  
  •  
  •  
export const enum Names {    Test = 'TEST'}

store 引入

  •  
  •  
  •  
  •  
  •  
  •  
import { defineStore } from 'pinia'import { Names } from './store-namespace'
export const useTestStore = defineStore(Names.Test, {
})

这个名称,也称为id,是必要的,Pania 使用它来将商店连接到 devtools。将返回的函数命名为use...是可组合项之间的约定,以使其使用习惯。

1.定义值

State 箭头函数 返回一个对象 在对象里面定义值

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import { defineStore } from 'pinia'import { Names } from './store-namespce'
export const useTestStore = defineStore(Names.Test, {     state:()=>{         return {             current:1         }     }})
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import { defineStore } from 'pinia'import { Names } from './store-namespce'
export const useTestStore = defineStore(Names.Test, {     state:()=>{         return {             current:1         }     },     //类似于computed 可以帮我们去修饰我们的值     getters:{
     },     //可以操作异步 和 同步提交state     actions:{
     }})

修改 State

1.State 是允许直接修改值的

例如current++

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
<template>     <div>         <button @click="Add">+</button>          <div>             {{Test.current}}          </div>     </div></template>
<script setup lang='ts'>import {useTestStore} from './store'const Test = useTestStore()const Add = () => {    Test.current++  // 注意这里} </script> 

VUEX 不允许如此修改,必须通过mutation提交,pinia可以直接修改

2.批量修改State的值 -- $patch

实例上有$patch方法可以批量修改多个值

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
<template>     <div>         <button @click="Add">+</button>          <div>             {{Test.current}}          </div>          <div>            {{Test.age}}          </div>     </div></template>
<script setup lang='ts'>import {useTestStore} from './store'const Test = useTestStore() const Add = () => {    Test.$patch({ // 注意这里       current:200,       age:300    })} </script> 

3.批量修改函数形式

推荐使用函数形式 可以自定义修改逻辑

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
<template>     <div>         <button @click="Add">+</button>          <div>             {{Test.current}}          </div>          <div>            {{Test.age}}          </div>     </div></template>
<script setup lang='ts'>import {useTestStore} from './store'const Test = useTestStore()const Add = () => {    Test.$patch((state)=>{ // 注意这里 .$patch       state.current++;       state.age = 40    })}
</script> 

4.通过原始对象修改整个实例

$state您可以通过将store的属性设置为新对象来替换store的整个状态

缺点就是 必须修改整个对象的所有属性

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
<template>     <div>         <button @click="Add">+</button>          <div>             {{Test.current}}          </div>          <div>            {{Test.age}}          </div>     </div></template>
<script setup lang='ts'>import {useTestStore} from './store'const Test = useTestStore()const Add = () => {    Test.$state = { // 注意这里 .$state       current:10,       age:30    }    } </script> 

5.通过actions修改

定义Actions

在actions 中直接使用this就可以指到state里面的值

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import { defineStore } from 'pinia'import { Names } from './store-naspace'export const useTestStore = defineStore(Names.TEST, {     state:()=>{         return {            current:1,            age:30         }     },
     actions:{ // 注意这里 setCurrent         setCurrent () {   // 这里不要写箭头函数,否则this指向会错误             this.current++          }     }})

使用方法直接在实例调用

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
<template>     <div>         <button @click="Add">+</button>          <div>             {{Test.current}}          </div>          <div>            {{Test.age}}          </div>     </div></template>
<script setup lang='ts'>import {useTestStore} from './store'const Test = useTestStore()const Add = () => {     Test.setCurrent() // 注意这里 setCurrent} </script> 

解构store

在Pinia是不允许直接解构,是会失去响应性的

  •  
  •  
  •  
  •  
  •  
const Test = useTestStore() 
const { current, name } = Test
console.log(current, name);

差异对比

修改Test current 解构完之后的数据不会变

而源数据是会变的

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
<template>  <div>origin value {{Test.current}}</div>  <div>    pinia:{{ current }}--{{ name }}    change :    <button @click="change">change</button>  </div></template>
<script setup lang='ts'>import { useTestStore } from './store' const Test = useTestStore()
const change = () => {   Test.current++}
const { current, name } = Test console.log(current, name); </script> </style>

解决方案可以使用 storeToRefs

  •  
  •  
  •  
  •  
  •  
import { storeToRefs } from 'pinia'
const Test = useTestStore()
const { current, name } = storeToRefs(Test) // storeToRefs

其原理跟toRefs 一样的给里面的数据包裹一层toref 源码 通过toRaw使store变回原始数据防止重复代理 循环store 通过 isRef isReactive 判断 如果是响应式对象直接拷贝一份给refs 对象 将其原始对象包裹toRef 使其变为响应式对象

 

图片

 

Actions,getters

Actions(支持同步异步)

1.同步 直接调用即可

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import { defineStore } from 'pinia'import { Names } from './store-naspace'export const useTestStore = defineStore(Names.TEST, {    state: () => ({        counter: 0,    }),    actions: {        increment() {            this.counter++        },        randomizeCounter() {            this.counter = Math.round(100 * Math.random())        },    },})
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
<template>     <div>         <button @click="Add">+</button>          <div>             {{Test.counter}}          </div>         </div></template>
<script setup lang='ts'>import {useTestStore} from './store'const Test = useTestStore()const Add = () => {     Test.randomizeCounter()}
</script> 

2.异步 可以结合async await 修饰

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import { defineStore } from 'pinia'import { Names } from './store-naspace'
type Result = {    name: string    isChu: boolean}
const Login = (): Promise<Result> => {    return new Promise((resolve) => {        setTimeout(() => {            resolve({                name: '小',                isChu: true            })        }, 3000)    })}
export const useTestStore = defineStore(Names.TEST, {    state: () => ({        user: <Result>{},        name: "123"    }),    actions: {        async getLoginInfo() {            const result = await Login()            this.user = result;        }    },})

template

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
<template>     <div>         <button @click="Add">test</button>          <div>             {{Test.user}}          </div>         </div></template>
<script setup lang='ts'>import {useTestStore} from './store'const Test = useTestStore()const Add = () => {     Test.getLoginInfo()} </script> 

3.多个action互相调用getLoginInfo setName

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
state: () => ({    user: <Result>{},    name: "default"}),actions: {    async getLoginInfo() {        const result = await Login()        this.user = result;        this.setName(result.name) // 这里    },    setName (name:string) {        this.name = name;    }},

getters

1.使用箭头函数不能使用this, this指向已经改变指向undefined 修改值请用state

主要作用类似于computed 数据修饰并且有缓存

  •  
  •  
  •  
getters:{    newPrice:(state)=> `$${state.user.price}`},

2.普通函数形式可以使用this

  •  
  •  
  •  
  •  
  •  
getters:{    newCurrent ():number {        return ++this.current    }},

3.getters 互相调用

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
getters:{    newCurrent ():number | string {        return ++this.current + this.newName    },    newName ():string {        return `$-${this.name}`    }},

API

1.$reset

重置store到他的初始状态

  •  
  •  
  •  
  •  
  •  
state: () => ({     user: <Result>{},     name: "default",     current:1}),

Vue 例如我把值改变到了10

  •  
  •  
  •  
const change = () => {     Test.current++}

调用$reset(); 将会把state所有值 重置回 原始状态

2.订阅state的改变

类似于Vuex 的abscribe 只要有state 的变化就会走这个函数

  •  
  •  
  •  
Test.$subscribe((args,state)=>{   console.log(args,state); })

返回值

 

图片

 

第二个参数 如果你的组件卸载之后还想继续调用请设置第二个参数

  •  
  •  
  •  
  •  
  •  
Test.$subscribe((args,state)=>{   console.log(args,state); },{  detached:true})

3.订阅Actions的调用

只要有actions被调用就会走这个函数

  •  
  •  
  •  
Test.$onAction((args)=>{   console.log(args); })

 

图片

 

pinia数据持久化

pinia和vuex都有一个通病 -- 页面刷新状态会丢失。 就是说数据持久化需要手动修改,插件本身不具备数据持久化的能力,当页面刷新或者应用更新后所有的状态数据均会丢失。

pinia是支持插件的,不知道各位小伙伴看过没有,这里引用的pinia数据持久化插件是pinia-plugin-persistedstate

安装:pinia-plugin-persistedstate

  •  
  •  
  •  
pnpm : pnpm i pinia-plugin-persistedstatenpm : npm i pinia-plugin-persistedstateyarn : yarn add pinia-plugin-persistedstate

配置插件pinia-plugin-persistedstate

在src目录下新增store文件夹,新增index.js

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
// pinia数据持久化存储import { createPinia } from "pinia"import { createPersistedState } from 'pinia-plugin-persistedstate'
const store = createPinia()    store.use(createPersistedState({        serializer: { // 指定参数序列化器            serialize: JSON.stringify,            deserialize: JSON.parse,        }    }))
export default store

在src新增store文件夹下,新增main.ts

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import { defineStore } from "pinia"
export const useMainStore = defineStore({    id: 'main',    state: () => ({        name: 'hello pinia'    }),    // persist: true,  开启数据持久化,所有store数据将被持久化到指定仓库    persist: { // 自定义数据持久化方式        // key: 'store-key', 指定key进行存储,此时非key的值不会持久化,刷新就会丢失        storage: window.localStorage, // 指定换成地址        // paths: ['nested.data'],// 指定需要持久化的state的路径名称        beforeRestore: context => {            console.log('Before' + context)        },        afterRestore: context => {            console.log('After'+ context)        },    },    getters: {        getName: (state) => {            return state.name         }    },    actions: {        SET_NAME (param:string) {            this.name = param        }    }})

再将store下的index.ts引入到入口文件main.ts文件里

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
import { createApp } from 'vue'import App from './App.vue'// 注入路由import router from './router'// 注入storeimport store from './store'import 'element-plus/dist/index.css'// 导入element-plus消息apiimport { ElMessage } from "element-plus"import { currentTiemStr } from "@/script/utils"
const app = createApp(App)    app.use(router)    app.use(store)    app.mount('#app')
// 全局常用api配置// 消息提示APiapp.config.globalProperties.$ElMessage = ElMessage// 获取时间戳方法app.config.globalProperties.$CurrentTiemStr = currentTiemStr

使用方法还是同上述1是一样的,当变化name值后,你刷新数据,会发现便后的name值一直都是最新值,至此,pinia使用插件pinia-plugin-persistedstate

pinia插件

由于有了底层 API 的支持,Pinia store 现在完全支持扩展。以下是你可以扩展的内容:

为 store 添加新的属性定义 store 时增加新的选项为 store 增加新的方法包装现有的方法改变甚至取消 action实现副作用,如本地存储仅应用插件于特定 store插件是通过 pinia.use()添加到 pinia 实例的。最简单的例子是通过返回一个对象将一个静态属性添加到所有 store。

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
 import { createPinia } from 'pinia'
// 在安装此插件后创建的每个 store 中都会添加一个名为 `secret` 的属性。// 插件可以保存在不同的文件中function SecretPiniaPlugin() {  return { secret: 'the cake is a lie' }}
const pinia = createPinia()// 将该插件交给 Piniapinia.use(SecretPiniaPlugin)
// 在另一个文件中const store = useStore()store.secret // 'the cake is a lie'
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
const __piniaKey = '__PINIAKEY__'    //定义兜底变量
type Options = {        key ? : string    }    //定义入参类型
//将数据存在本地const setStorage = (key: string, value: any): void => {    localStorage.setItem(key, JSON.stringify(value))}
//存缓存中读取const getStorage = (key: string) => {    return (localStorage.getItem(key) ? JSON.parse(localStorage.getItem(key) as string) : {})}
//利用函数柯丽华接受用户入参const piniaPlugin = (options: Options) => {      //将函数返回给pinia  让pinia  调用 注入 context    return (context: PiniaPluginContext) => {         const {            store        } = context;
        const data = getStorage(`${options?.key ?? __piniaKey}-${store.$id}`)
        store.$subscribe(() => {             setStorage(`${options?.key ?? __piniaKey}-${store.$id}`, toRaw(store.$state));         })
        //返回值覆盖pinia 原始值        return {             ...data         }     } }

//初始化piniaconst pinia = createPinia()
//注册pinia 插件pinia.use(piniaPlugin({    key: "pinia"}))

参考地址:

https://blog.csdn.net/qq1195566313/category_11672479.html

References

[1] 官方文档Pinia: https://pinia.vuejs.org/
[2] git 地址 : https://github.com/vuejs/pinia

posted @ 2023-02-28 15:00  RuoVea  阅读(1257)  评论(1编辑  收藏  举报