项目代码:GitHub 分享链接
第一次尝试以构建案例的方式写博客,从构思大纲到构建项目花了很多时间,希望能够帮助到大家,如果博客对你有帮助请留下你的点赞和关注叭!❤️❤️❤️
符合直觉的 Vue.js 状态管理库
在学习之前,先来了解什么是 pinia 以及为什么要使用 pinia
比如说这是一个项目的基础结构,当组件很少的时候可以通过组件通信来解决,但当组件多了起来
这样再去实现组件通信就显得尤其困难,所以需要一个 库 可以替我们 集中式 存放很多组件中需要共享的数据(状态),这就是 pinia
的官方介绍 符合直觉的 Vue.js 状态管理库,比如常见的用户登录数据、购物车数据都属于多组件共享的数据,需要一个地方集中式的存储和读取等等。
pinia
相比于 另一个状态管理库 vuex
在使用上最大的特点就是符合直觉,在 pinia
中可以做到 所见即所得,拿取的数据就可以进行直接修改,而不需要再去定义复杂的 mutations
或者 actions
去调用数据,接下来在学习中去感受它的特性。
注意:为了尽量覆盖所有的知识点,案例中可能存在一些不合理的地方,不适于在真正的环境中使用。
创建 vue3
项目
$ npm create vue@latest
$ pnpm create vue@latest
$ yarn create vue@latest
$ bun create vue@latest
按照本图配置即可,然后我们使用 WebStrom
打开项目(VsCode
等均可)
安装依赖
$ npm install
打开 package.json
运行 “dev”
确保项目可以正确启动
注意 :
因为这里是使用
vite
构建项目,且预先选择了pinia
引入,如果是已有的项目请按照如下的方式:安装
$ yarn add pinia # 或者使用 npm $ npm install pinia
创建实例并且挂载到
app
上import './assets/main.css' import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' const app = createApp(App) app.use(createPinia()) app.mount('#app')
清除默认的样式
App.vue
中的填充部分main.ts
中的 import './assets/main.css'
引入 Arco Design
组件库:为了方便开发这里引入现有的组件库
安装
# npm
$ npm install --save-dev @arco-design/web-vue
# yarn
$ yarn add --dev @arco-design/web-vue
全局引入
import { createApp } from 'vue'
import ArcoVue from '@arco-design/web-vue'
import App from './App.vue'
import '@arco-design/web-vue/dist/arco.css'
const app = createApp(App);
app.use(ArcoVue);
app.mount('#app');
什么是组件库?
前端组件库是一组可重复使用的前端组件和样式,旨在简化Web开发过程,提高开发效率和一致性。这些组件通常包括按钮、表单、导航栏、模态框、图表等,可以轻松集成到项目中,使开发人员能够快速构建用户界面。
按照这个结构搭建一个基础的架子,数据使用假数据填充
最终结果:
源码:
<template>
<div class="userMessage">
<div class="showAvatar">
<div class="avatar"></div>
<div class="userName">*Soo_Young*</div>
</div>
<div class="basicMessage">
<a-descriptions style="margin-top: 20px" :data="data" :size="size" title="基本信息" :column="1"/>
</div>
</div>
</template>
<script setup lang="ts">
import {ref} from "vue";
const birthday = new Date(2004, 8, 14);
const formatDate = () : string => {
let year = birthday.getFullYear();
let month = birthday.getMonth();
let day = birthday.getDate();
let monthStr;
let dayStr;
// 格式化月份和日期为两位数
if (month < 10) {
monthStr = '0' + month.toString();
} else {
monthStr = month;
}
if (day < 10) {
dayStr = '0' + day.toString();
} else {
dayStr = day;
}
return year + '-' + monthStr + '-' + dayStr;
}
const data = ref([{
label: '姓名',
value: '*Soo_Young*'
}, {
label: '电话',
value: '123-1234-1234',
}, {
label: '住址',
value: '北京'
}, {
label: '生日',
value: formatDate(),
},])
const size = "large";
</script>
<style scoped>
.userMessage {
display: flex;
flex-direction: column;
background-color: #f5f6f7;
}
.showAvatar {
background-color: #ffffff;
margin-top: 50px;
display: flex;
width: 1500px;
height: 100px;
padding-left: 30px;
}
.showAvatar .avatar {
width: 80px;
height: 80px;
border-radius: 80px;
background-image: url("https://thirdwx.qlogo.cn/mmopen/vi_32/ibO6F2GNoPIxP9ibg7CQNymDUYZegTLFILic1KM6NmUYMXHKKNZBnFd7dpPrLnvxZTVUibicqBov4vib7e0mfGah0SGw/132");
background-repeat: no-repeat;
background-size: cover;
}
.showAvatar .userName {
margin-top: 40px;
margin-left: 30px;
font-weight: 700;
}
.basicMessage {
background-color: #ffffff;
margin-top: 20px;
padding: 50px;
}
</style>
先来明确一下这个案例的需求
- 实现从全局库中拿取数据并且展示
- 实现用户名的修改
- 新增计算生日,用户查询今天距离自己生日还有几天
- 新增修改用户信息界面,并且将数据存储到
localStage
中实现持久化存储
因为需求都比较简单,这里就不做过多赘述了,下面开始开发。
在存储数据之前,先需要一个仓库,所以这里先来创建一个全局的状态库,Store
是一个保存:状态、业务逻辑 的实体,每个组件都可以读取、写入它。
state
、getter
、action
,相当于组件中的: data
、 computed
和 methods
;这里先使用其中的 state
部分。工程化建议
- 将库相关的代码放到
/store
文件夹下- 文件命名为所存储的数据比如
/user.ts
在 /store
目录下创建 user.ts
这里模拟的是已经向后端请求到用户数据的情况,正常应该是向后端请求信息,且比较重要的信息是不会明文存储的。
import { defineStore } from "pinia"
export const useUserStore = defineStore('counter', {
state: () => ({
}),
})
state
中的数据就是全局库中存储的数据,接下来,将上面的模拟数据加到 state
中:
// 定义并暴露一个store
export const useUserStore = defineStore('counter', {
state: () => ({
name: "*Soo_Young*",
phone: "123-1234-1234",
address: "北京",
birthday: new Date(2004, 8, 14)
}),
})
返回 App.vue
组件来使用其中存储的数据
// 引入 store 并且使用
import {useUserStore} from "@/stores/user";
const userStore = useUserStore();
// 直接拿取数据
const birthday = userStore.birthday;
const data = ref([{
label: '姓名',
value: userStore.name
}, {
label: '电话',
value: userStore.phone,
}, {
label: '住址',
value: userStore.address
}, {
label: '生日',
value: formatDate(),
},])
经过这样的处理,展示效果和之前完全相同,不知道大家有没有体会到其 符合直觉 的特点,拿取数据就可以直接使用。
使用解构来简化调用
userStore.
前缀,这里可以使用解构将其解构为新的 ref
对象,但注意,直接解构会使得数据失去响应式,而使用 toRefs
API 又会导致其中所有的部分都被结构为 ref
对象,所以这里使用新的 API:storeToRefs
它只会将数据部分转为 ref
对象。const userStore = useUserStore();
const {name, birthday, phone, address} = storeToRefs(userStore);
// 再使用就需要添加 value
let year = birthday.value.getFullYear();
let month = birthday.value.getMonth();
let day = birthday.value.getDate();
实现用户名的修改
先来构建一个表单界面,使用 v-model
实现双向绑定
<div class="changeName">
更改用户名:<input type="text" v-model="newName"><br><br>
<a-button type="primary" @click="changeName">提交</a-button>
</div>
const newName = ref(); // 用户的新用户名
const changeName = () => {
name.value = newName.value;
console.log(name.value);
}
.changeName {
margin-top: 20px;
background-color: white;
padding: 50px;
}
当点击提交的时候会调用这个方法来赋值给库中的数据最终实现数据的修改
这种直接修改的方式在
vuex
中无法想象的,显然这样的调用更加的方便和符合直觉。
修改数据的三种方式
除了上面的直接修改,还有两种修改方式可以实现修改
直接修改,注意下面两种方式 不要混用
# 直接使用
userStore.name = Kk
# 解构
name.value = Kk
批量修改:可以使用一条语句一次修改多个数据
userStore.$patch({
name:Kkq,
phome:'122-132-122'
})
构建 action
方法来修改:上面提到 action
其实就类似于 method
export const useUserStore = defineStore('counter', {
actions: {
changeName() {
this.name = "Kkq";
},
},
state: () => ({
name: "*Soo_Young*",
phone: "123-1234-1234",
address: "北京",
birthday: new Date(2004, 8, 14)
}),
})
接下来在 @click
后直接调用这个方法,就可以实现修改
<a-button type="primary" @click="userStore.changeName()">提交</a-button>
计算如果写在组件中会显得比较冗余,而这种对于数据的 拓展 就可以使用计算属性来书写,也就是上面提到的
getters
。
书写计算生日的 getters
getters: {
dayToBirth: (state) => {
let birthday = new Date(state.birthday);
let today = new Date();
birthday.setFullYear(today.getFullYear());
if (today > birthday) {
birthday.setFullYear(today.getFullYear() + 1);
}
let diffInMilliseconds:number = Number(birthday) - Number(today);
return Math.ceil(diffInMilliseconds / (1000 * 60 * 60 * 24));
},
},
在
getters
中数据,是一个方法,其返回值就是得到的数据,调用时会传入值state
,可以直接从state
中去拿取数据和修改。
调用数据
在 basicMessage
盒子中去使用上面的数据即可实现效果
<div class="basicMessage">
<a-descriptions style="margin-top: 20px" :data="data" :size="size" title="基本信息" :column="1"/>
<div class="dayToBirth">
距离您的生日还有 {{userStore.dayToBirth}} 天
</div>
</div>
上面的用户名受限于
pinia
刷新即丢失的限制导致刷新页面还是会读取原来的数据,这里可以使用localStage
来持久化存储数据;所以需要一个方式来监听姓名的修改,当用户修改后就将信息存储到localStage
中,且刷新后再读取也是读取localStage
中的值。
这就需要提供监听能力的方法,来监听 store
中的数据的变化,当其变化后就做出修改:
userStore.$subscribe((mutate, state) => {
localStorage.setItem('userName', JSON.stringify(name.value));
})
在Pinia中,
userStore.$subscribe()
方法用于订阅Store的状态变化,并在状态变化时执行回调函数。回调函数接收两个参数:
- mutate: 这是一个函数,用于提交mutation以更新Store的状态。通过调用
mutate
函数,你可以在订阅状态变化时进行一些逻辑处理,例如更新Store中的数据。- state: 这是当前Store的状态对象。你可以通过
state
参数访问Store中的状态,并在订阅状态变化时对状态进行操作或检查状态的变化。这里暂时没有用到这两个参数
实现刷新后读取
这时候 store
中获取数据就可以直接从 localStage
中拿取了
state: () => ({
name: JSON.parse(localStorage.getItem('userName') as string) || 'Kkq',
phone: "123-1234-1234",
address: "北京",
birthday: new Date(2004, 8, 14)
}),
使用
JSON.parse()
将其转为正常结构,再通过||
提供为null
时的默认值
这样即使当页面刷新数据也不会丢失了。
创建项目
$ npm create vue@latest
$ pnpm create vue@latest
$ yarn create vue@latest
$ bun create vue@latest
引入 pinia
依赖
$ yarn add pinia
# 或者使用 npm
$ npm install pinia
挂载到 main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
定义 store
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
double: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
},
})
解构
const userStore = useUserStore();
const {name, birthday, phone, address} = storeToRefs(userStore);
// 再使用就需要添加 value
let year = birthday.value.getFullYear();
let month = birthday.value.getMonth();
let day = birthday.value.getDate();
监听修改
userStore.$subscribe((mutate, state) => {
localStorage.setItem('userName', JSON.stringify(name.value));
})