Pinia - 符合直觉的Vue.js状态管理库

集中式状态(数据)管理,把需要共享的数据交给pinia管理

准备一个效果

pinia_example

搭建 pinia 环境

第一步:npm install pinia

第二步:操作src/main.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { createApp } from 'vue'
import App from './App.vue'

/* 引入createPinia,用于创建pinia */
import { createPinia } from 'pinia'

/* 创建pinia */
const pinia = createPinia()
const app = createApp(App)

/* 使用插件 / 安装pinia */
app.use(pinia)
app.mount('#app')

此时开发者工具中已经有了pinia选项

存储+读取数据

  1. Store是一个保存:状态业务逻辑 的实体,每个组件都可以读取写入它。

  2. 它有三个概念:stategetteraction,相当于组件中的: datacomputedmethods

  3. 具体编码:src/store/count.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 引入defineStore用于创建store
    import {defineStore} from 'pinia'

    // 定义并暴露一个store
    // 推荐和定义hooks一样用use命名
    export const useCountStore = defineStore('count',{
    // 动作
    actions:{},
    // 状态
    state(){
    return {
    sum:6
    }
    },
    // 计算
    getters:{}
    })
  4. 具体编码:src/store/talk.ts

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 引入defineStore用于创建store
    import {defineStore} from 'pinia'

    // 定义并暴露一个store
    export const useTalkStore = defineStore('talk',{
    // 动作
    actions:{},
    // 状态
    state(){
    return {
    talkList:[
    {id:'yuysada01',content:'你今天有点怪,哪里怪?怪好看的!'},
    {id:'yuysada02',content:'草莓、蓝莓、蔓越莓,你想我了没?'},
    {id:'yuysada03',content:'心里给你留了一块地,我的死心塌地'}
    ]
    }
    },
    // 计算
    getters:{}
    })
  5. 组件中使用state中的数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <template>
    <h2>当前求和为:{{ sumStore.sum }}</h2>
    </template>

    <script setup lang="ts" name="Count">
    // 引入对应的useXxxxxStore
    import {useSumStore} from '@/store/sum'

    // 调用useXxxxxStore得到对应的store
    const sumStore = useSumStore()

    // 虽然sumStore里面的sum是个ObjectRefImpl对象,但是这里,不能.value,会报错
    // 如let obj = reactive({a:1,b:1,c:ref(9)}),
    // 这里的c就应该直接obj.c,而不能加.value,因为外面已经帮我们处理好了
    console.log(sumStore.sum) // 直接拿
    console.log(sumStore.$state.sum) // 也可以这样,但是没必要
    </script>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <template>
    <ul>
    <li v-for="talk in talkStore.talkList" :key="talk.id">
    {{ talk.content }}
    </li>
    </ul>
    </template>

    <script setup lang="ts" name="Count">
    import axios from 'axios'
    import {useTalkStore} from '@/store/talk'

    const talkStore = useTalkStore()
    </script>

修改数据(三种方式)

  1. 第一种修改方式,直接修改(vuex中不允许直接修改,要借助mutation)

    1
    countStore.sum = 666
  2. 第二种修改方式:批量修改

    1
    2
    3
    4
    countStore.$patch({
    sum:999,
    school:'atguigu'
    })
  3. 第三种修改方式:借助action修改(action中可以编写一些业务逻辑)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import { defineStore } from 'pinia'

    export const useCountStore = defineStore('count', {
    /*************/
    actions: {
    //加
    increment(value:number) {
    if (this.sum < 10) {
    //操作countStore中的sum
    this.sum += value
    }
    },
    //减
    decrement(value:number){
    if(this.sum > 1){
    this.sum -= value
    }
    }
    },
    /*************/
    })
  4. 组件中调用action即可

    1
    2
    3
    4
    5
    // 使用countStore
    const countStore = useCountStore()

    // 调用对应action
    countStore.incrementOdd(n.value)

storeToRefs

在组件中,我们每次使用store里的值,都需要在模版中如{{countStore.sum}},每次使用都需要前面这一大长串。于是我们可以想到解构赋值:
const { sum, school } = countStore ,但是这样数据就失去了响应式!
虽然可以借助toRefs:const { sum, school } = Refs(countStore)
但是我们打印一下这个Refs(countStore),可以发现它把整个store里面所有的属性(数据和方法)都打成了ref,虽然实现了,但是代价有点大
所以,我们可以使用 —— storeToRefs

  • 借助storeToRefsstore中的数据转为ref对象,方便在模板中使用。

  • 注意:pinia提供的storeToRefs只会将数据做转换,不会对方法进行ref包裹;而VuetoRefs会转换store中所有数据。

  • 所取属性的数据类型:

    • 基本类型需要包裹,脚本文件中.value使用
    • 对象类型可以直接解构出来(还是响应式的),也可以包裹(脚本文件中.value使用)
      直接解构:const { talkList } = talkStore,然后直接talkList使用
      包裹解构:const { talkList } = storeToRefs(talkStore),在脚本文件中需要talkList.value使用

    和toRefs基本一致?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="count">
<h2>当前求和为:{{sum}}</h2>
</div>
</template>

<script setup lang="ts" name="Count">
import { useCountStore } from '@/store/count'
/* 引入storeToRefs */
import { storeToRefs } from 'pinia'

/* 得到countStore */
const countStore = useCountStore()
/* 使用storeToRefs转换countStore,随后解构 */
const {sum} = storeToRefs(countStore)
</script>

getters

  1. 概念:当state中的数据,需要经过处理后再使用时,可以使用getters配置。

  2. 追加getters配置。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    // 引入defineStore用于创建store
    import {defineStore} from 'pinia'

    // 定义并暴露一个store
    export const useCountStore = defineStore('count',{
    // 动作
    actions:{
    /************/
    },
    // 状态
    state(){
    return {
    sum:1,
    school:'atguigu'
    }
    },
    // 计算
    getters:{
    // 如果不用this,这里就可以写成 箭头函数
    bigSum:(state):number => state.sum *10,
    upperSchool():string{
    // this就是store
    return this.school.toUpperCase()
    },
    printThis:()=>this, // 得到的是undefined
    }
    })
  3. 组件中读取数据:

    1
    2
    const {increment,decrement} = countStore
    let {sum,school,bigSum,upperSchool} = storeToRefs(countStore)

$subscribe

通过 store 的 $subscribe() 方法侦听 state 及其变化

  • mutate:本次修改的信息
  • state:真正的数据
1
2
3
4
5
// 组件中
talkStore.$subscribe((mutate,state)=>{
console.log('LoveTalk',mutate,state)
localStorage.setItem('talk',JSON.stringify(talkList.value))
})
1
2
3
4
5
6
7
state(){
return {
// as 是断言,能取出来的字符串
// 第一次是null,应该给个空数组默认值
talkList: JSON.parse(localStorage.getItem('talkList') as string) || []
}
}

store组合式写法

就和setup语法糖很像

  • 数据直接用ref、reactive去定义

  • 直接写函数,就是actions函数

  • getters直接写computed

  • 必须return出去

  • 缺点:页面数据多的时候,一堆return

  • 优点:不用写那么多层次和结构,清晰一点,易于维护

    选项式优点:易于理解和迁移,与传统的Vuex风格相似
    缺点:代码量增加,不够简洁,尤其是在大型项目中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {defineStore} from 'pinia'
import axios from 'axios'
import {nanoid} from 'nanoid'
import {reactive} from 'vue'

export const useTalkStore = defineStore('talk',()=>{
// talkList就是state
const talkList = reactive(
JSON.parse(localStorage.getItem('talkList') as string) || []
)

// getATalk函数相当于action
async function getATalk(){
// 发请求,下面这行的写法是:连续解构赋值+重命名
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json')
// 把请求回来的字符串,包装成一个对象
let obj = {id:nanoid(),title}
// 放到数组中
talkList.unshift(obj)
}
return {talkList,getATalk}
})