VUE3基础之Pinia

107 阅读13分钟

Pinia是VUE3的一个状态管理工具,官方文档说Pinia是一个符合直觉的Vue.js状态管理库,相比于VUEXPinia没有那么复杂的流程,非常的简单而又直接。

VUEX和Pinia

VUE2的VUEX和VUE3Pinia都叫做集中式状态管理,这个状态说白了就是数据,就是用于管理存储数据的,包括REACT的Redux。集中式的意思就是把所有要管理的数据都放在一个容器里统一管理,相对的就是分布式,比如分布式系统。

什么时候会用到这个集中式状态管理呢,就是当组件很多且来回嵌套层级很大的时候,此时单纯的父子组件传值已经不方便了,因为可能要传很多次,或者说组件之间根本就没有嵌套关系,父子组件无法传值,此时就可以用到集中式状态管理。如果只是某个组件自己要用或者说自己这个组件和上下嵌套的组件会用到的时候,就不要用集中式状态管理,直接把数据存储在该组件内或者用父子组件传值就好了。

Pinia的使用如何存储和读取数据

事前准备

我们先搭建一个场景,页面上有两块内容,一块是一个加减功能,另一块是获取动漫名称的内容。

CountView.vue

<template>
  <div class="part-count-view">
    <div class="sum">当前求和为:{{ sum }}</div>
    <div class="btn">
      <el-select v-model="num" style="width: 120px">
        <el-option label="1" :value="1"></el-option>
        <el-option label="2" :value="2"></el-option>
        <el-option label="3" :value="3"></el-option>
        <el-option label="4" :value="4"></el-option>
      </el-select>
      <el-button type="primary" @click="addFun" style="margin-left: 10px"></el-button>
      <el-button type="primary" @click="subFun"></el-button>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
let sum = ref(1);
let num = ref(1);

function addFun() {
  sum.value += num.value;
}
function subFun() {
  sum.value -= num.value;
}
</script>

<style lang="scss" scoped>
.part-count-view {
  box-sizing: border-box;
  height: 100%;
  padding: 10px;

  .sum {
    margin-top: 40px;
    margin-bottom: 50px;
  }
}
</style>

GetAnimeView.vue

<template>
  <div class="part-anime-view">
    <div class="btn">
      <el-button @click="addAnime">添加一部动漫</el-button>
      <el-button @click="subAnime">减少一部动漫</el-button>
    </div>
    <div class="container">
      <ul>
        <li v-for="(item, index) in animeList" :key="index">{{ item }}</li>
      </ul>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
let allAnimes = ref(["海贼王", "火影忍者", "死神", "家庭教师", "全职猎人", "JOJO"]);

let index = ref(1);
let animeList = ref<string[]>(["海贼王"]);

function addAnime() {
  if (index.value >= 6)
    return ElMessage({
      message: "已经没有动漫了!",
      type: "warning",
    });
  index.value++;
  animeList.value = allAnimes.value.slice(0, index.value);
}

function subAnime() {
  if (index.value <= 1)
    return ElMessage({
      message: "最少保留一部动漫!",
      type: "warning",
    });
  index.value--;
  animeList.value = allAnimes.value.slice(0, index.value);
}
</script>

<style lang="scss" scoped>
.part-anime-view {
  height: 100%;

  .btn {
    margin-top: 20px;
  }

  .container {
    margin-top: 10px;
    display: flex;
    justify-content: center;

    ul {
      width: 50%;
    }
  }
}
</style>

index.vue

<template>
  <div class="screen-view">
    <div class="top">
      <CountView></CountView>
    </div>
    <div class="bottom">
      <GetAnimeView></GetAnimeView>
    </div>
  </div>
</template>
<script lang="ts" setup>
import CountView from "./components/CountView.vue";
import GetAnimeView from "./components/GetAnimeView.vue";
</script>

<style scoped lang="scss">
.screen-view {
  height: 100%;
  padding: 10px;
  box-sizing: border-box;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;

  .top {
    width: 500px;
    height: 200px;
    border-radius: 10px;
    background-color: pink;
    margin-bottom: 30px;
  }

  .bottom {
    width: 500px;
    height: 200px;
    border-radius: 10px;
    background-color: skyblue;
  }
}
</style>

页面展示

1750764940066.png

搭建pinia环境

想使用pinia需要先安装,如果在创建项目之前就配置好了就不用再安装了。

npm i pinia

安装完pinia以后就要去搭建pinia环境,首先去main.ts文件中引入创建pinia的函数,然后创建pinia,最后再把pinia应用到app上。

main.ts


import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// 第一步:引入创建pinia的函数
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'

const app = createApp(App)
// 第二步:创建pinia  最好再创建完app以后
const pinia = createPinia()
// 第三步:将pinia应用到app上
app.use(pinia)

app.use(router)
app.use(ElementPlus)

app.mount('#app')

这样以后pinia的环境就搭建好了。

使用pinia存储+读取数据

pinia强调一个东西叫做分类(这个分类跟前面说的把数据都放在一起统一管理不冲突),首先考虑项目中有多少个组件,然后考虑要把哪些数据存储进去。根据我们上面准备好的前提,一共有两个组件CountViewGetAnimeView两个组件,CountView组件有一个求和数据sumGetAnimeView组件有一个展示动漫的数据animeList,这两个数据最后会存储在pinia中。

首先在src文件夹下创建一个文件夹叫做store,熟悉VUE2的都知道这是配置VUEX的文件夹,在VUE3里这里就是配置pinia的。然后我们因为有两个组件CountViewGetAnimeView,所以这里面就要分类了,分成两个ts文件去分别存储对应组件的数据,为了起名规范这两个ts文件就叫做count.tsanime.ts

然后在文件里我们要从pinia引入一个函数叫做defineStore(虽然前面讲带define的函数可以省略引入,但是这个不能),然后执行这个函数赋值给一个变量,这就相当于创建一个store专门存储CountView组件的数据,变量名最好用Hooks的形式,这里就叫做useCountStore

store/count.ts

import { defineStore } from 'pinia'

const useCountStore = defineStore()

然后就是要往defineStore函数里面传参,有两个参数,第一个就相当于是这个storeid值,就是代表这个sotre的名称,最好和文件名相同,所以这里就传count,第二个参数是一个配置对象,其中有一个属性叫做state,表示状态,其实也就是数据,这个是存储数据的地方(跟VUEX一样),如果是VUE2或者REACT,这里就是一个对象,但是VUE3里这是一个函数,然后return出去一个对象,存储的数据就放在这里面,这里存储一下CountView组件的求和数据sum,然后给一个默认值7

store/count.ts

import { defineStore } from 'pinia'

const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7
    }
  }
})

创建好store以后我们肯定要用到它,所以这里还要给它暴露出去。

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7
    }
  }
})

暴露出去以后呢我们就要在组件里面引入了,引入这个useCountStore以后我们执行它,就可以获取到这个store,然后把它赋值给一个变量,我们就可以通过操作这个变量去操作这个store了,打印一下看看。

CountView.vue

<template>
  <div class="part-count-view">
    <div class="sum">当前求和为:{{ sum }}</div>
    <div class="btn">
      <el-select v-model="num" style="width: 120px">
        <el-option label="1" :value="1"></el-option>
        <el-option label="2" :value="2"></el-option>
        <el-option label="3" :value="3"></el-option>
        <el-option label="4" :value="4"></el-option>
      </el-select>
      <el-button type="primary" @click="addFun" style="margin-left: 10px"></el-button>
      <el-button type="primary" @click="subFun"></el-button>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";

// 引入创建的store
import { useCountStore } from "@/store/count";
// 执行这个store
const countStore = useCountStore()
console.log(countStore);

let sum = ref(1);
let num = ref(1);

function addFun() {
  sum.value += num.value;
}
function subFun() {
  sum.value -= num.value;
}
</script>

<style lang="scss" scoped>
.part-count-view {
  box-sizing: border-box;
  height: 100%;
  padding: 10px;

  .sum {
    margin-top: 40px;
    margin-bottom: 50px;
  }
}
</style>

1750769312543.png

可以看到打印的是一个Proxy对象,这就说明store的数据是响应式的,里面就有一个属性sum,就是我们放在state里的数据,所以我们就可以直接通过countStore.sum去获取这个数据。下面还有一个属性叫做$store,这是一个对象,里面存储的数据也是我们存放在state里的数据,所以我们也可以通过countStore.$state.sum去获取,推荐第一种,简单。所以组件里面就不要自己定义sum了,直接获取store里的使用就可以了(实际项目种最好是中间在用一个变量过渡一下,不要直接修改state里的数据,下面会讲)。

CountView.vue

<template>
  <div class="part-count-view">
    <!-- <div class="sum">当前求和为:{{ sum }}</div> -->
    <div class="sum">当前求和为:{{ countStore.sum }}</div>
    <div class="btn">
      <el-select v-model="num" style="width: 120px">
        <el-option label="1" :value="1"></el-option>
        <el-option label="2" :value="2"></el-option>
        <el-option label="3" :value="3"></el-option>
        <el-option label="4" :value="4"></el-option>
      </el-select>
      <el-button type="primary" @click="addFun" style="margin-left: 10px"></el-button>
      <el-button type="primary" @click="subFun"></el-button>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import { useCountStore } from "@/store/count";
const countStore = useCountStore();
// let sum = ref(1)

let num = ref(1);

function addFun() {
  countStore.sum += num.value;
  // sum.value += num.value
}
function subFun() {
  countStore.sum -= num.value;
  // sum.value -= num.value
}
</script>

<style lang="scss" scoped>
.part-count-view {
  box-sizing: border-box;
  height: 100%;
  padding: 10px;

  .sum {
    margin-top: 40px;
    margin-bottom: 50px;
  }
}
</style>

CountView组件写完了,那么GetAnimeView组件自然也是一样的道理。

store/anime.ts

import { defineStore } from 'pinia'

export const useAnimeStore = defineStore('anime', {
  state() {
    return {
      animeList: ['海贼王']
    }
  }
})

GetAnimeView

<template>
  <div class="part-anime-view">
    <div class="btn">
      <el-button @click="addAnime">添加一部动漫</el-button>
      <el-button @click="subAnime">减少一部动漫</el-button>
    </div>
    <div class="container">
      <ul>
        <li v-for="(item, index) in animeStore.animeList" :key="index">{{ item }}</li>
        <!-- <li v-for="(item, index) in animeList" :key="index">{{ item }}</li> -->
      </ul>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import { useAnimeStore } from "@/store/anime";
import { ElMessage } from "element-plus";
let allAnimes = ref(["海贼王", "火影忍者", "死神", "家庭教师", "全职猎人", "JOJO"]);

let index = ref(1);
// let animeList = ref<string[]>(['海贼王'])
const animeStore = useAnimeStore();

function addAnime() {
  if (index.value >= 6)
    return ElMessage({
      message: "已经没有动漫了!",
      type: "warning",
    });
  index.value++;
  animeStore.animeList = allAnimes.value.slice(0, index.value);
  // animeList.value = allAnimes.value.slice(0, index.value);
}

function subAnime() {
  if (index.value <= 1)
    return ElMessage({
      message: "最少保留一部动漫!",
      type: "warning",
    });
  index.value--;
  animeStore.animeList = allAnimes.value.slice(0, index.value);
  // animeList.value = allAnimes.value.slice(0, index.value);
}
</script>

<style lang="scss" scoped>
.part-anime-view {
  height: 100%;

  .btn {
    margin-top: 20px;
  }

  .container {
    margin-top: 10px;
    display: flex;
    justify-content: center;

    ul {
      width: 50%;
    }
  }
}
</style>

Pinia如何修改数据

讲完了pinia的搭建配置,也讲了pinia如何去存储读取数据,现在该讲如何去修改数据了,pinia修改数据的方式一共有三种。

第一种修改方式 直接修改源数据

我们前面演示的时候也做了修改,就是直接去修改获取到的数据。

CountView.vue

import { ref } from "vue";
import { useCountStore } from "@/store/count";
const countStore = useCountStore();
// let sum = ref(1)

let num = ref(1);

function addFun() {
  // 直接修改 store 里的源数据
  countStore.sum += num.value;
  // sum.value += num.value
}
function subFun() {
  // 直接修改 store 里的源数据
  countStore.sum -= num.value;
  // sum.value -= num.value
}

这种方式也就只有在pinia里是可用的了,在VUEX会报错,所以这也是pinia为什么被称为符合直觉(拿到数据就可以直接改)的状态管理库,但是一般不建议使用这种直接修改源数据的方式。

第二种修改方式 $patch

如果我们存储的数据很多的时候,继续用第一种修改方式就比较麻烦,我们先多添加几个数据试试:

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
      // 多添加的数据
      name: 'Moon',
      age: 18
    }
  }
})

然后在组件引入,添加一个按钮,点击按钮以后让sum+1nameage都修改。

CountView.vue

// 新增修改按钮
function change() {
  countStore.sum += 1;
  countStore.name = "月亮";
  countStore.age = 19;
}

但是这样观感不够好,看起来很繁琐,如果数据再多一点,就要输入很多countStore.,所以我们可以使用$patch方法去批量修改。

如果细心的话会在前面打印的时候发现countStore里面$patch属性,这是一个方法,可以传入一个对象参数,把要修改的数据作为属性,修改的值作为属性值,去进行一个批量修改。

CountView.vue

// 新增修改按钮
function change() {
  countStore.$patch({
    sum: 8,
    name: "月亮",
    age: 18,
  });
}

但是如果参数里有不属于原本的数据,就会报错,比如原本storestate的数据只有sumnameage,我们传一个address进去,代码就会报错。

1750852809079.png

第三种修改方式 actions

actionsVUEX里就是用于修改数据的,里面会有修改数据的方法,VUE2和VUE3有很多地方都相似,所以这里actions也是用于修改数据的。

在使用actions之前我们要先去定义一下这个属性,actions是一个对象,可以存储很多个属性,每个属性都是一个方法,用于去响应store的一些操作,就比如修改数据的操作,我们举个例子,声明一个addFn方法用于代替原本组件的加按钮的方法。

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
    }
  },
  // 放置的是一个一个的方法,用于响应store的一些操作
  actions: {
    addFn() {}
  }
})

但是我们要知道,我们计算加法的时候要根据选择不同的值去加,所以我们还要传参过去,所以这里还要有一个参数去接收传过来的值,如果要传多个,就多设置几个参数。

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
    }
  },
  // 放置的是一个一个的方法,用于响应store的一些操作
  actions: {
    addFn(value) {
      console.log(value);
    }
  }
})

CountView.vue

// 加按钮
function addFun() {
  // countStore.sum += num.value;
  // sum.value += num.value
  countStore.addFn(num.value);
}

这个时候就能获取到我们选择的值,那光有选择的值也不行啊,还得有原本的sum值啊,这个该怎么取呢,难不成直接从state里取吗?但是stateactions是不同的模块,是不能互相取值的,所以这个时候我们用到了this,哎好久不见的this,在这里打印一下this看看是啥。

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
    }
  },
  // 放置的是一个一个的方法,用于响应store的一些操作
  actions: {
    addFn(value) {
      console.log(value);
    }
  }
})

1750854785478.png

哎,可以看到这个this就是store,所以我们可以直接通过this.sum去获取到sum的值,所以最终代码就是:

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
    }
  },
  // 放置的是一个一个的方法,用于响应store的一些操作
  actions: {
    addFn(value) {
      this.sum += value
    }
  }
})

此时actions的功能就基本实现了,但是这不还是拿过来源数据进行直接修改吗?有什么意义呢,其实意义在于,actions里面可以写一些逻辑,比如说限制sum最大只能为10

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
    }
  },
  // 放置的是一个一个的方法,用于响应store的一些操作
  actions: {
    addFn(value) {
      if (this.sum < 10) {
        this.sum += value
      }
    }
  }
})

但是这个逻辑我们在组件里面也可以写啊。

CountView.vue

// 加按钮
function addFun() {
  // countStore.sum += num.value;
  // sum.value += num.value

  if (countStore.sum < 10) {
    countStore.sum += num.value;
  }
}

但是当这个方法用的比较多的时候,就可以减少代码量,实现代码复用,而且实际项目中不会这么去写,而是用一个中间变量去过渡,比如这样:

CountView.vue

import { ref } from "vue";
import { useCountStore } from "@/store/count";
const countStore = useCountStore();

let num = ref(1);
// 用一个中间变量来过渡,先把源数据值赋值给变量
let sum = countStore.sum

// 加按钮
function addFun() {
  sum += num.value
  // 修改以后再把变量的值设置为源数据的值
  countStore.setSum(sum)
}

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
    }
  },
  // 放置的是一个一个的方法,用于响应store的一些操作
  actions: {
    // 把中间变量的值赋值给源数据
    setSum(value) {
      this.sum = value
    }
  }
})

storeToRefs

我们前面讲读取数据的时候都是怎么读取的,通过countStores.sum或者countStroe.$state.sum进行读取,但是这样是不是觉得很不方便,前面还得进行countStore.,所以我们可以换一种方式进行读取数据。

那有人就会想到,我们能不能给它解构赋值出来呢?我们去试一下:

CountView.vue

<template>
  <div class="part-count-view">
    <!-- 将解构赋值出来的数据放在页面上 -->
    <div class="sum">当前求和为:{{ sum }}</div>
    <div class="btn">
      <el-select v-model="num" style="width: 120px">
        <el-option label="1" :value="1"></el-option>
        <el-option label="2" :value="2"></el-option>
        <el-option label="3" :value="3"></el-option>
        <el-option label="4" :value="4"></el-option>
      </el-select>
      <el-button type="primary" @click="addFun" style="margin-left: 10px"></el-button>
      <el-button type="primary" @click="subFun"></el-button>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref } from "vue";
import { useCountStore } from "@/store/count";
const countStore = useCountStore();

let num = ref(1);
// 通过解构赋值的形式获取sum
let { sum } = countStore;

// 加按钮
function addFun() {
  // 使用actions的方式操作sum
  countStore.addFn(num.value);
  console.log(countStore.sum);  // 打印store上的sum
}

function subFun() {
  countStore.sum -= num.value;
}
</script>

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
    }
  },
  actions: {
    // 操作sum的方法
    addFn(value) {
      this.sum += value
    }
  }
})

1750938364330.png

会发现页面数据不变,但是实际上数据已经发生改变了,这一点像不像讲声明响应式数据的时候,从响应式数据上解构赋值出来的数据失去响应式,最后用toRefs去解决这个问题,那么我们是否也可以用toRefs去解决呢?我们试一下。

CountView.vue

// 解构赋值的时候用toRefs包裹
let { sum } = toRefs(countStore);
console.log(toRefs(countStore));  // 打印一下

1750938739677.png

此时解构出来的sum确实变成响应式的了,也能正常使用,那我们是否就是说可以用这个方法呢?虽然使用toRefs可以解决问题,但是最好不用,我们打印这个被toRefs包裹起来的数据看看是个什么东西。

1750938860323.png

会发现它把里面所有的属性都变成响应式的了,其实我们只要sum变成响应式的就好了,而且那addFn是一个方法,你给变成响应式的干嘛。那有人又会说了,那我们用toRef不就可以了吗,那我们问,如果我们解构的数据很多难不成一个一个去解构吗,所以这里有一个专门用于store数据的方法storeToRefs,当然要先从pinia里面引入。

CountView.vue

import { storeToRefs } from "pinia";
// 解构赋值的时候用storeToRefs包裹
let { sum } = storeToRefs(countStore);
console.log(storeToRefs(countStore)); // 打印一下

1750939092391.png

可以发现解构出来的数据是响应式的了,能够实现功能,那我们再看看被storeToRefs包裹起来的数据是什么样的。

1750939166121.png

可以看到里面没有什么杂七杂八的东西,只有存储的数据,没有方法之类的东西,所以这种方法更适合去解构store里面的数据。

getters

VUEX里面也有getterspinia里面也有,那这个getters是干嘛的呢?

其实就相当于是一个计算属性computed,使用方式也是一样的,getters是一个对象,里面的每一个属性都是一个计算属性,通过return去返回一个被二次计算后的值。而且里面可以接受一个参数,这个参数就是state,所以在getters里面是可以获取到state里面的值的,从而进行二次计算。当然这里面也可以用this,不过VUE3已经减少this的使用,所以建议直接用state

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
    }
  },
  actions: {
    // 操作sum的方法
    addFn(value) {
      this.sum += value
    }
  },
  // 类似于计算属性
  getters: {
    // 声明一个 sum * 10 的计算属性
    bigSum(state) {
      return state.sum * 10
    }
  }
})

而且getters里面的数据是可以被stroeToRefs去解构出来为响应式变量的。

// 解构赋值的时候用storeToRefs包裹
let { sum, bigSum } = storeToRefs(countStore);
console.log(storeToRefs(countStore)); // 打印一下

1750939919894.png

因为减少this的使用,所以我们可以在getters里面使用箭头函数更方便。

store/count.ts

  getters: {
    bigSum: state => state.sum * 10
  }

$subscrie

我们前面讲了store的一个方法$patch,是用于批量修改数据的,这次讲一下$subscribe方法,这个方法很简单,类似于监视器watch,我们往这个方法里面传入一个回调函数,当store里的数据发生变化的时候,这个回调函数就会去调用,回调函数接收两个参数mutatestatemutate是触发事件的一些相关信息,state就是数据改变以后的store里的state

CountView.vue

// $subscribe方法
countStore.$subscribe((mutate, state) => {
  console.log("数据发生了变化");
  console.log("mutate", mutate);
  console.log("state", state);
});

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
    }
  },
  actions: {
    // 操作sum的方法
    addFn(value) {
      this.sum += value
    }
  },
  getters: {
    bigSum: state => state.sum * 10
  }
})

1750941325734.png

通过打印可以看出来state就是修改以后的store,这个一眼就看的明白,我们着重看一下mutatemutate里面有一个属性叫做storeId,就是我们前面讲的配置store的时候设置的名字,这里是告诉你哪个store的数据被修改了,还有一个属性叫做effect,是一个对象,里面有一个属性key,这个就是告诉我们修改的是哪个数据,而newValueoldValue就好理解了,就是说数据修改之后的值和修改之前的值。

那如果同时修改多个数据会怎样呢?我们试一下,先给state新增一个数据a,值就为1,然后我们在修改sum的时候把这个a也修改一下。

CountView.vue

// $subscribe方法
countStore.$subscribe((mutate, state) => {
  console.log("数据发生了变化");
  console.log("mutate", mutate);
  console.log("state", state);
});
// 加按钮
function addFun() {
  countStore.addFn(num.value);
  countStore.a++;
}

store/count.ts

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
      a: 1 // 新增一个数据a
    }
  },
  actions: {
    addFn(value) {
      this.sum += value
    }
  },
  getters: {
    bigSum: state => state.sum * 10
  }
})

1750941670340.png

可以看到如果同时修改多个数据,$subscribe方法只会触发一次,并且key指的是最后修改的数据。

store的组合式写法

我们做了半天,有没有发现这个store是选项式写法,里面的stateactionsgetters都是一个一个的选项,而VUE3不是组合式开发吗,所以我们给它改成组合式的写法。

store/count.ts 选项式写法

import { defineStore } from 'pinia'

export const useCountStore = defineStore('count', {
  state() {
    return {
      sum: 7,
    }
  },
  actions: {
    addFn(value) {
      this.sum += value
    }
  },
  getters: {
    bigSum: state => state.sum * 10
  }
})

组合式写法的时候defineStore的第二个参数就不是一个对象了,而是一个函数,然后在里面通过ref去声明一个变量作为state或者用computed去声明一个计算属性也就是gettersactions方法就直接通过function去定义一个方法,最后return出去。

store/count.ts 组合式写法

import { ref } from 'vue'
import { computed } from 'vue'
export const useCountStore = defineStore('count', () => {

  // 声明state的数据
  let sum = ref(7)

  // 声明action的方法
  function addFn(value) {
    sum.value += value
  }

  // 声明getters的计算属性
  let bigSum = computed(() => {
    return sum.value * 10
  });

  // 最后return出去,不return出去就获取不到里面的数据和方法
  return { sum, addFn, bigSum }
})

这么看来,是不是很像刚开始讲的setup,其实也差不多。

1750942575658.png