pinia状态管理

71 阅读3分钟

准备一个案例

新建工程,创建components/Count.vue

<template>
    <div class="count">
        <h2> 当前求和为:{{sum}}</h2>
        <!-- options 里面的value加:,可以让i从字符串变成number类型,也可以在v-model后面加number转number类型 -->
        <select v-model.number="n">
            <option :value="i" v-for="i in 10" :key="i">{{ i }}</option>
        </select>
        <button @click="add">加</button>
        <button @click="min">减</button>
    </div>
</template>

<script>
export default {
    name: 'Counter'
}
</script>

<script setup>
import { ref } from 'vue'
const sum = ref(1)
const n = ref(1)

function add() {
    sum.value += n.value
}

function min() {
    sum.value -= n.value
}

</script>

<style lang="scss" scoped>
.count {
    background-color: skyblue;
    padding: 10px;
    border-radius: 10px;
    box-shadow: 0 0 10px;
}

select, button {
    margin: 0 10px;
    height: 25px;
}
</style>

创建components/Love.vue,npm install axios; npm install nanoid

<template>
    <div class="love">
        <button @click="getNewLove">获取一句土味情话</button>
        <ul>
            <li v-for="(item) in loveList" :key="item.id" v-html="item.text"></li>
        </ul>
    </div>
</template>

<script>
export default {
    name: "Love"
}
</script>

<script setup>
import { reactive } from 'vue';
import axios from 'axios';
import { nanoid } from 'nanoid';

const loveList = reactive([
    {id: 1, text: "心心念念全是你的脸。"},
    {id: 2, text: "十五、说海枯石烂太远,说山盟海誓太大,我只愿能和你做一条线上的蚂蚁,一条爱情锁上的囚徒。"}
])

function getNewLove() {
  // 这里不处理会有跨端问题,配置vite.config.js中的proxy,get里面的url不能包含http,否则就不会走proxy
    axios.get('/words/api.php?return=json').then(res => {
        loveList.unshift({
            id: nanoid(),
            text: res.data.word
        })
        
    }).catch(err => {
        console.log(err);
    })
}

</script>

<style lang="scss" scoped>
.love {
    background-color: olive;
    padding: 10px;
    box-shadow: 0 0 10px;
    border-radius: 10px;
}
</style>

配置vite.config.js,解决跨域问题

server: {
  host: '0.0.0.0',
  port: 80,
  proxy: {
    '/words/api.php': {
      target: 'https://api.1314.cool',
      changeOrigin: true,
      secure: false, // 如果 HTTPS 证书有问题
    }
  },
},

pinia环境

npm i pinia

// main.js 中的内容
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

const pinia = createPinia()
const app = createApp(App)
app.use(pinia)

app.mount('#app')

使用流程

创建src/store/Count.ts文件以及src/store/Love.ts文件, 名字和要使用pinia保存状态的vue文件名称保持一样。

Count.ts内容:

import { defineStore } from "pinia";
// 使用defineStore创建一个store,第一个入参是名称,第二个入参是对象,state方法里放要保存的数据
// 使用export导出后外面才能使用
export const useCountStore = defineStore("count", {
  state: () => ({
    sum: 6,
  }),
});

使用者Count.vue调用

// 先引入
import { useCountStore } from '@/store/Count'
// 获取保存状态的countStore
const countStore = useCountStore()
// 模板中使用
<h2> 当前求和为:{{countStore.sum}}</h2>

完整读取数据示例

<template>
    <div class="count">
        <h2> 当前求和为:{{countStore.sum}}</h2>
        <!-- options 里面的value加:,可以让i从字符串变成number类型,也可以在v-model后面加number转number类型 -->
        <select v-model.number="n">
            <option :value="i" v-for="i in 10" :key="i">{{ i }}</option>
        </select>
        <button @click="add">加</button>
        <button @click="min">减</button>
    </div>
</template>

<script>
export default {
    name: 'Counter'
}
</script>

<script setup>
import { ref } from 'vue'
  // 引入useCountStore
import { useCountStore } from '@/store/Count'

const n = ref(1)
// 使用useCountStore,得到一个专门保存count相关的store
const countStore = useCountStore()

function add() {
    countStore.sum += n.value
}

function min() {
    countStore.sum -= n.value
}

</script>

<style lang="scss" scoped>
.count {
    background-color: skyblue;
    padding: 10px;
    border-radius: 10px;
    box-shadow: 0 0 10px;
}

select, button {
    margin: 0 10px;
    height: 25px;
}
</style>

同样的方式,Love.ts

import { defineStore } from "pinia";

export const useLoveStore = defineStore("love", {
  state: () => ({
    loveList: [ 
        {id: 1, text: "心心念念全是你的脸。"},
        {id: 2, text: "十五、说海枯石烂太远,说山盟海誓太大,我只愿能和你做一条线上的蚂蚁,一条爱情锁上的囚徒。"}
    ],
  }),
});

Love.vue使用完全代码

<template>
    <div class="love">
        <button @click="getNewLove">获取一句土味情话</button>
        <ul>
            <li v-for="(item) in loveStore.loveList" :key="item.id" v-html="item.text"></li>
        </ul>
    </div>
</template>

<script>
export default {
    name: "Love"
}
</script>

<script setup>
import axios from 'axios';
import { nanoid } from 'nanoid';
  // 引入useLoveStore
import { useLoveStore } from '@/store/Love';
// 使用useLoveStore,得到一个专门保存loveList相关的store
const loveStore = useLoveStore();

function getNewLove() {
    axios.get('/words/api.php?return=json').then(res => {
        loveStore.loveList.unshift({
            id: nanoid(),
            text: res.data.word
        })
        
    }).catch(err => {
        console.log(err);
    })
}

</script>

<style lang="scss" scoped>
.love {
    background-color: olive;
    padding: 10px;
    box-shadow: 0 0 10px;
    border-radius: 10px;
}
</style>

修改数据

上面示例中,我们直接修改数据,这种方式如果数据很多的时候不方便维护

function add() {
    countStore.sum += n.value
}

比如我们修改Count.ts

export const useCountStore = defineStore("count", {
  state: () => ({
    sum: 6,
    school: "河南师范大学",
    location: "河南新乡"
  }),
});

这时候如果要修改数据,则是这样

// 这种修改是没问题的,但是如果十几个字段这么写不美观,而且底层渲染的时候,mutation set会执行三次 
countStore.sum += n.value
countStore.school = "school"
countStore.location = "location"

**第二种方式 ** $patch批量修改数据

// 这种方式$patch一次性批量修改数据内容,避免了mutation set执行三次的问题 
countStore.$patch({
    sum: 999,
    school: '北京大学',
    location: '北京'
})

第三种方式 actions方式

Count.ts修改为

import { defineStore } from "pinia";

export const useCountStore = defineStore("count", {
    actions: {
        add(n) {
            this.sum+=n;
        },
        minus(n) {
            this.sum-=n;
        }
    },
    state: () => ({
        sum: 6,
        school: "河南师范大学",
        location: "河南新乡"
    }),
});

Count.vue中修改加减方法

function add() {
    countStore.add(n.value)
}

function min() {
    countStore.minus(n.value)
}

至此,Count已改造完成,同理,Love.ts Love.vue修改流程一样

// Love.ts 中的actions
actions: {
  addLove() {
     axios.get('/words/api.php?return=json').then(res => {
      this.loveList.unshift({
          id: nanoid(),
          text: res.data.word
      })

  }).catch(err => {
      console.log(err);
  })
  },
},
  
// Love.vue修改getNewLove
function getNewLove() {
  loveStore.addLove();
}

模板读取优化

现在数据都是从countStore读取

<h2> 当前求和为:{{countStore.sum}}</h2>

可以使用解构进行优化,下面这种写法读取没问题,但是失去了响应式

const { sum } = countStore
<h2> 当前求和为:{{sum}}</h2>

使用toRefs优化,下面这种写法有了响应式,但是会把Count.ts中的方法和属性全都加上ref包裹,代价太大

const { sum } = toRefs(countStore)

可以直接打印看下

console.log(toRefs(countStore))
image-20250810144522070转存失败,建议直接上传图片文件

最优解是使用pinia中的storeToRefs,只会关注store中的数据,不会对方法进行ref包裹

const { sum } = storeToRefs(countStore)
console.log(storeToRefs(countStore))
image-20250810144649340转存失败,建议直接上传图片文件

getters

// Count.ts中:
export const useCountStore = defineStore("count", {
    getters: {
        // 对数据不满意,对数据进行加工,返回一个新的数据
        doubleSum: (state) => state.sum * 2,
    },
});

// 使用
const { sum, doubleSum } = storeToRefs(countStore)
<h2>@@@ {{doubleSum}}</h2>

$subscribe的使用,store中数据变化时调用

const loveStore = useLoveStore();
// 当数据更新时会执行subscribe
loveStore.$subscribe((mutation, state) => {
    console.log("LoveStore 更新了!");
});

可以在这里进行数据持久化

loveStore.$subscribe((mutation, state) => {
    localStorage.setItem('loveList', JSON.stringify(state.loveList));
});

在love.ts中,loveList数据先从本地读取,如果没读取到内容则返回空数组[]

state: () => ({
  loveList: JSON.parse(localStorage.getItem("loveList")) || [],
}),