pinia简介和setup语法糖

1.pinia的基本特点

pinia同样是一个Vue 状态管理工具,它和vuex有很多相似的地方。本质上他是vuex团队核心成员开发的,在vuex上面提出了一些改进。与vuex相比,pinia去除了vuex中对于同步函数Mutations和异步函数Actions的区分。直接在Actions中便能够使用同步和异步方法(在vuex的开发计划中也将会除去同步和异步的区分)。其次相比于vuex,pinia对于typescript的支持性更好,友好的devTools支持,pinia只有1kb,简化了很多方法的写法。由于vuex比较完善,因此,pinia更加适合小型项目,vuex更加适合大型项目。

2.基本配置和使用

利用vue-cli创建一个pinia项目(这里选择创建vue3项目)

  1. vue create pinia

在项目中安装pinia

  1. npm install pinia@next

项目中导入pinia

  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. import { createPinia } from 'pinia'
  4. //需要注意的是从pinia中结构出来的createPinia是一个函数,挂载需要先调用执行
  5. createApp(App).use(createPinia()).mount('#app')

配置状态管理专用文件,在根目录下创建一个store文件,并新建一个index.js文件

  1. import {defineStore} from 'pinia';
  2. export const userTestStore = defineStore({//需要注意的是,defineStore返回的是一个回调方法
  3. id:'test',//test是该状态管理的唯一标志也可以使用defineStore(id,{});的形式
  4. state(){
  5. return {
  6. name:'hello pinia',
  7. age:20
  8. }
  9. },
  10. getters:{
  11. testGetters(){
  12. return this.name+'...';//直接利用this便能够获取到里面的内容不需要使用state中间对象
  13. }
  14. },
  15. actions:()=>{
  16. addAge:function(){
  17. setInterval(()=>{
  18. this.age++;
  19. },1000)
  20. }
  21. }
  22. })

3.pinia传参与调用

下面给出调用store里面的带参方法例子:

  1. //在状态管理工具中定义addAge函数
  2. actions:{
  3. addAge(gap){
  4. this.age+=gap;
  5. }
  6. }
  1. //组件中导入对应状态管理工具
  2. import { userTestStore } from "./store";
  3. const store = userTestStore();
  4. const { addAge } = store;//解构出store实例里面的addAge方法

组件中使用方法如下:

  1. <div>output age:{{ store.age }}</div>
  2. <button @click="addAge(10)">click for add age</button>

需要注意的是,在官网中有这么一句话:

直接修改解构出来的 age是不ref类型,给出如下的错误案例:

  1. <div>error:{{ age }}</div>
  2. <button @click="test">change error age</button>
  1. let { age } = store;
  2. function test() {
  3. console.log(" error add age");
  4. age++;
  5. }

解决方法,使用pinia里面的storeToRefs强转:

  1. import { storeToRefs } from "pinia";
  2. let { age } = storeToRefs(userTestStore());
  3. function test() {
  4. age.value++;//注意这里要加上.value因为被转化成了ref类型
  5. }
  6. //这样就能够是实现有效修改了

4.setup语法糖

接下来说一下vue3中中setup语法糖的基本使用方式。语法糖主要是为了简化代码的书写方式,只要知道怎么用就行,实际编译时,编译器会自动转化成大多数基础开发者用的那种写法进行编译。给出如下使用例子:

  1. <template>
  2. <div>{{ name1 }}</div>
  3. <div @click="test">
  4. <div>nickname:{{ user.username }}</div>
  5. <div>age:{{ user.age }}</div>
  6. <div>{{ data }}</div>
  7. </div>
  8. </template>
  9. <script setup>
  10. import { ref, reactive } from "vue";
  11. const name1 = "hello world";
  12. let data = ref("default"); //ref使用方法
  13. const user = reactive({ //reactive使用方法
  14. username: "name",
  15. age: 18,
  16. });
  17. function test() {
  18. user.name += "...";
  19. data.value = "new data";//注意不要忘了ref操作数据的特点
  20. }
  21. </script>

整体上,就是不需要通过export导出,setup挂载之后会自动帮你导出。

(2)组件的引入和导出,直接使用import 导入对应的组件就能够直接使用了,给出如下例子:

  1. <!--components目录下新建一个msg.vue组件-->
  2. <template>
  3. <div>
  4. {{ msg }}
  5. </div>
  6. </template>
  7. <script setup>
  8. let msg = "hello!"
  9. </script>
  1. <!--App.vue组件中使用msg组件-->
  2. <template>
  3. <msg />
  4. </template>
  5. <script setup>
  6. import msg from "@/components/msg.vue";
  7. </script>

(3)自定义属性

  1. import { defineProps} from "vue";
  2. let props = defineProps({
  3. text: String
  4. });

但是我们发现,官方并没有提供对应的默认值设置方式,通过查阅发现,默认值的设置可以利用withDefaults这一宏命令来实现,其使用方法如下:

  1. <script setup lang="ts">
  2. //defineProps, withDefaults两个都是宏命令,不需要进行手动导入
  3. const props = withDefaults(
  4. defineProps<{
  5. text: string;
  6. }>(),
  7. {
  8. text: "default Data",
  9. }
  10. );
  11. </script>

需要注意很重要的一点,这里不要使用 vue-cli 来创建项目,经过我反复鞭尸,发现vue-cli中使用的vue create “项目名”创建的项目对于lang=ts的声明不支持,会报两个loader没有加载等一系列错误,反正就是会导致项目运行不起来。解决方法是通过vite脚手架来安装项目:

  1. //安装初始化的vue3项目
  2. yarn create @vitejs/app my-vue-app --template vue
  3. //加载依赖
  4. yarn

最终得到的效果是能够使用默认值,补充一些typescript的新型用法:

  1. const props = withDefaults(
  2. defineProps<{
  3. text?:string;//表示text为可选参数,这个时候也就是说下面的default将失效
  4. }>(),
  5. {
  6. text: "default Data",
  7. }
  8. );
  1. //利用接口声明类型
  2. interface testType {
  3. name: string;
  4. age: number;
  5. }
  6. const props = withDefaults(
  7. defineProps<{
  8. text: testType;
  9. }>(),
  10. {
  11. text: {
  12. name: "flying_dark_feather",
  13. age: 20,
  14. },
  15. }
  16. );
  17. //但是在使用的时候需要利用传参的形式传递实际数据,否则会被识别成字符串而报错

(3)setup子组件触发父组件中的函数

子组件书写对应的函数体:

  1. <template>
  2. <div @click="test">这是info组件</div>
  3. </template>
  1. <script setup>
  2. //触发的组件需要使用defineEmits进行声明,返回一个函数(event,...args),该函数的第一个参数为事件名,其他参数为
  3. //传递给第一个事件函数的参数
  4. const emits = defineEmits(["handleClick", "handleChange"]);
  5. function test() {
  6. emits("handleClick", "来自子组件的参数");
  7. }
  8. </script>

父组件接受对应的函数体:

  1. <template>
  2. <info @handleClick="test" />
  3. </template>
  4. <script setup>
  5. import info from "./components/info.vue";
  6. function test(res) {
  7. console.log("点击事件被触发,子组件传递参数:", res);
  8. }
  9. </script>
  1. //获取组件信息可以使用如下两种方式
  2. //vue2
  3. mounted: function () {
  4. console.log("输出数据", this.$refs.testRef);
  5. //testRef等价为一个名字,每个ref对应一个实例化的dom对象
  6. },
  7. //vue3 setup获取(这里指的是获取不是导出)
  8. <script setup>
  9. import info from "./components/info.vue";
  10. import { onMounted, ref } from "vue";
  11. const testRef = ref(null);//主要是为了防止未定义报错
  12. onMounted(() => {
  13. //直接拿到节点里面的testRef属性,也即自定义组件的实例
  14. console.log("输出数据:", testRef.value);
  15. });
  16. console.log("testRef",testRef.value);//实实在在的ref变量
  17. </script>
  18. 看到上面的两种获取方式,我的确懵了,最后找到了答案:
  19. 如果ref属性加在普通元素上,那么this.$refs.name则指向该DOM元素
  20. 如果ref属性加在组件上,那么this.$refs.name指向该组件实例
  21. 通过打印两个testRef发现两个表示的其实并不是一个东西
  22. 给出解释:之所以加上testRef主要是考虑到运行前的检测,自我感觉这个解释不太专业
  23. 通过使用setup定义组件,导致里面定义的变量和函数在父组件中无法获取。解决方法:使用defineEmit定义导出:
  24. defineExpose({//defineExpose同样是宏定义,不需要导入
  25. count,
  26. handle,
  27. });
  28. 补充说明:不建议直接利用ref修改数据,性能上不是很友好。
  29. 5useSlots追踪父组件中定义的插槽内容(需要导入)
  30. <!--父组件-->
  31. <template>
  32. <info>
  33. <template #header><div>这是header数据</div></template>
  34. <!--或者v-slot:header-->
  35. <template #footer><div>这是footer数据</div></template>
  36. </info>
  37. </template>
  38. <!--子组件-->
  39. <template>
  40. <div>这是info组件</div>
  41. <slot name="header" />
  42. <slot name="footer" />
  43. </template>
  44. <script setup lang="ts">
  45. import { onMounted, useSlots } from "@vue/runtime-core";
  46. const slots = useSlots();//调用useSlots函数获取父组件中填充的slots内容
  47. onMounted(() => {
  48. console.log("输出插槽内容:", slots);
  49. });
  50. </script>
  51. 6useAttrs获取父组件中定义的属性
  52. <!--父组件-->
  53. <info attr="test"></info>
  54. <!--子组件-->
  55. <script setup lang="ts">
  56. import { onMounted, useAttrs, useSlots } from "@vue/runtime-core";
  57. const getAttr = useAttrs();
  58. onMounted(() => {
  59. console.log("输出attr:", getAttr);//获取父组件的属性
  60. });
  61. </script>
  62. 当然,获取了父组件的属性还是用处不是很大,因为一个组件一般父组件的内容子组件还是知道的。
  63. <template>
  64. <div class="test-div" @click="clickFun">这是info组件</div>
  65. </template>
  66. <script setup lang="ts">
  67. import { ref } from "vue";
  68. let setColor = ref("pink");
  69. function clickFun() {
  70. setColor.value = "red";
  71. }
  72. </script>
  73. <style>
  74. .test-div {
  75. color: v-bind(setColor);
  76. }
  77. </style>