5. pinia 传送门
5.1【准备一个效果】
购物车效果
进货信息
显示/隐藏 vue
<!-- 1 定义组件模版 -->
<template>
<div class="goodsApp">
进货信息
<fieldset >
<legend>商品名称:</legend>
<input type="text" v-model="name" />
</fieldset>
<fieldset >
<legend>商品单价:</legend>
<input type="text" v-model="price" />
</fieldset>
<fieldset >
<legend>数量:</legend>
<button type="button" style=";border-radius: 2px; background-color: rgb(144, 156, 166);padding: 5px;" @click="increament(1)">+</button><input type="text" v-model="count" count /><button type="button" style="border-radius: 2px; background-color: rgb(144, 156, 166);padding: 5px;" @click="increament(-1)">-</button>
</fieldset>
<fieldset >
<legend>商品总价:</legend>
{{ amount }}
</fieldset>
</div>
</template>
<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="PiniaDemo1">
import { computed, ref } from 'vue';
//定义数据
let name = ref('魔方法式软面包')
let price = ref(19.8)
let count = ref(1)
//计算属性
let amount = computed(()=>{
return (price.value * count.value).toFixed(2)
})
function increament(num){
count.value += num
}
</script>
<!-- 3 定义样式 -->
<style scoped>
.goodsApp{
width: 500px;
}
input[count]{
width: 40px;
text-align: center;
}
</style>
5.2【搭建 pinia 环境】
第一步:npm install pinia
第二步:操作src/main.ts
ts
import { createApp } from 'vue'
import App from './App.vue'
/*1 引入createPinia,用于创建pinia */
import { createPinia } from 'pinia'
/*2 创建pinia */
const pinia = createPinia()
const app = createApp(App)
/*3 使用插件 */{}
app.use(pinia)
app.mount('#app')
此时开发者工具中已经有了pinia
选项
5.3【存储+读取数据】
Store
是一个保存:状态、业务逻辑 的实体,每个组件都可以读取、写入它。它有三个概念:
state
、getter
、action
,相当于组件中的:data
、computed
和methods
。具体编码:
src/store/cart.ts
tsimport { defineStore } from "pinia"; export const useCart = defineStore('cart', { //数据状态 state: () => { return { name: '鲁花压榨葵花仁油5L', price: 19.8, count: 1 } } })
组件中使用
state
中的数据vue<!-- 1 定义组件模版 --> <template> <div class="goodsApp"> 进货信息 <fieldset > <legend>商品名称:</legend> <input type="text" v-model="cartStore.name" /> </fieldset> <fieldset > <legend>商品单价:</legend> <input type="text" v-model="cartStore.price" /> </fieldset> <fieldset > <legend>数量:</legend> <button type="button" style=";border-radius: 2px; background-color: rgb(144, 156, 166);padding: 5px;" @click="increament(1)">+</button><input type="text" v-model="cartStore.count" count /><button type="button" style="border-radius: 2px; background-color: rgb(144, 156, 166);padding: 5px;" @click="increament(-1)">-</button> </fieldset> <fieldset > <legend>商品总价:</legend> {{ amount }} </fieldset> </div> </template> <!-- 2 定义组件逻辑 --> <script setup lang="ts" name="PiniaDemo1"> import { computed, ref } from 'vue'; import { useCartStore } from "@/store/Cart"; let cartStore = useCartStore(); console.log("cartStore ",cartStore) //计算属性 let amount = computed(()=>{ return (cartStore.price * cartStore.count).toFixed(2) }) function increament(num){ cartStore.count += num } </script> <!-- 3 定义样式 --> <style scoped> .goodsApp{ width: 500px; } input[count]{ width: 40px; text-align: center; } </style>
5.4.【修改数据】(三种方式)
第一种修改方式,直接修改
tscartStore.count = 999
第二种修改方式:批量修改
tscountStore.$patch({ sum:999, school:'atguigu' })
第三种修改方式:借助
action
修改(action
中可以编写一些业务逻辑){传送门}jsimport { defineStore } from "pinia"; export interface ICartStore{ name:string, price:number, count:number } export const useCartStore = defineStore('cart', { actions:{ changeCart({name,price,count}:ICartStore):void{ this.name = name; this.count = count this.price = price }, plusCount(){ this.count++ }, subCount(){ if( this.count == 1){ return } this.count-- } }, //数据状态 state: () => { return { name: '鲁花压榨葵花仁油5L', price: 19.8, count: 1 } } })
组件中调用
action
即可vue<!-- 1 定义组件模版 --> <template> <div class="goodsApp"> 进货信息 <fieldset> <legend>商品名称:</legend> {{ cartStore.name }} </fieldset> <fieldset> <legend>商品单价:</legend> <input type="text" v-model="cartStore.price" /> </fieldset> <fieldset> <legend>数量:</legend> <button type="button" class="increament" @click="increament(1)">+</button> <input type="text" v-model="cartStore.count" count /> <button type="button" class="increament" @click="increament(-1)">-</button> <button type="button" class="increament" @click="patch()">patch补丁</button> <button type="button" class="increament" @click="useAction()">使用action</button> </fieldset> <fieldset> <legend>商品总价:</legend> {{ amount }} </fieldset> </div> </template> <!-- 2 定义组件逻辑 --> <script setup lang="ts" name="PiniaDemo1"> import { computed, ref } from 'vue'; import { useCartStore } from "@/store/Cart"; let cartStore = useCartStore(); console.log("cartStore ", cartStore) //计算属性 let amount = computed(() => { return (cartStore.price * cartStore.count).toFixed(2) }) function increament(num) { cartStore.count += num } //批量更新 function patch() { // cartStore.$patch(state => state.name = 'newName') cartStore.$patch((state) => { state.name = '[中华特色]高邮馆 高邮咸鸭蛋100克10只简装' state.price = 100 state.count = 5 }) // cartStore.$patch({ // name: '[中华特色]高邮馆 高邮咸鸭蛋50克10只简装', // price: 100, // count: 5 // }) } function useAction() { // cartStore.changeCart({ // name: '[中华特色]高邮馆 高邮咸鸭蛋50克10只简装', // price: 100, // count: 5 // }) // cartStore.plusCount() //通用的逻辑 逻辑复用 cartStore.subCount() } </script> <!-- 3 定义样式 --> <style scoped> .goodsApp { width: 500px; } input[count] { width: 40px; text-align: center; } .increament { margin: 0 3px; } </style>
5.5.【storeToRefs】
- 借助
storeToRefs
将store
中的数据转为ref
对象,方便在模板中使用。 - 注意:
pinia
提供的storeToRefs
只会将数据做转换,而Vue
的toRefs
会转换store
中数据。
vue
<!-- 1 定义组件模版 -->
<template>
<div class="goodsApp">
进货信息
<fieldset>
<legend>商品名称:</legend>
{{ name }}
</fieldset>
<fieldset>
<legend>商品单价:</legend>
<input type="text" v-model="price" />
</fieldset>
<fieldset>
<legend>数量:</legend>
<button type="button" class="increament" @click="increament(1)">+</button>
<input type="text" v-model="count" count />
<button type="button" class="increament" @click="increament(-1)">-</button>
<button type="button" class="increament" @click="patch()">patch补丁</button>
<button type="button" class="increament" @click="useAction()">使用action</button>
</fieldset>
<fieldset>
<legend>商品总价:</legend>
{{ amount }}
</fieldset>
</div>
</template>
<!-- 2 定义组件逻辑 -->
<script setup lang="ts" name="PiniaDemo1">
import { computed, ref, toRefs } from 'vue';
import { useCartStore } from "@/store/Cart";
let cartStore = useCartStore();
//storeToRefs
console.log(toRefs(cartStore))
let {price ,count,name} = toRefs(cartStore);
console.log("cartStore ", cartStore)
//计算属性
let amount = computed(() => {
return (price.value * count.value).toFixed(2)
})
function increament(num:number) {
count.value += num
}
//批量更新
function patch() {
// cartStore.$patch(state => state.name = 'newName')
cartStore.$patch((state) => {
state.name = '[中华特色]高邮馆 高邮咸鸭蛋100克10只简装'
state.price = 100
state.count = 5
})
// cartStore.$patch({
// name: '[中华特色]高邮馆 高邮咸鸭蛋50克10只简装',
// price: 100,
// count: 5
// })
}
function useAction() {
// cartStore.changeCart({
// name: '[中华特色]高邮馆 高邮咸鸭蛋50克10只简装',
// price: 100,
// count: 5
// })
// cartStore.plusCount()
//通用的逻辑 逻辑复用
cartStore.subCount()
}
</script>
<!-- 3 定义样式 -->
<style scoped> .goodsApp {
width: 500px;
}
input[count] {
width: 40px;
text-align: center;
}
.increament {
margin: 0 3px;
}
</style>
5.6.【getters】
概念:当
state
中的数据,需要经过处理后再使用时,可以使用getters
配置。追加
getters
配置。jsimport { defineStore } from "pinia"; export interface ICartStore{ name:string, price:number, count:number } export const useCartStore = defineStore('cart', { actions:{ changeCart({name,price,count}:ICartStore):void{ this.name = name; this.count = count this.price = price }, plusCount(){ this.count++ }, subCount(){ if( this.count == 1){ return } this.count-- } }, //数据状态 state: () => { return { name: '鲁花压榨葵花仁油5L', price: 19.8, count: 1 } }, getters:{ // amount(state){ // return (state.price * state.count).toFixed(2) // } amount():number{ let res = Number((this.price * this.count).toFixed(2)); return res } } })
组件中读取数据:
jsimport { useCartStore } from "@/store/Cart"; let cartStore = useCartStore(); let {price ,count,name,amount} = toRefs(cartStore);
5.7.【$subscribe】
类似于 Vuex 的 【subscribe方法】,通过 store 的 $subscribe()
方法侦听 state
及其变化
ts
//数据状态
state: () => {
const cartStoreState:ICartStore = JSON.parse(localStorage.getItem("cartStoreState") as string)
let {name,price,count} = cartStoreState
return {
name: name,
price: price,
count: count
}
},
ts
//订阅
cartStore.$subscribe((mutation, state)=>{
localStorage.setItem("cartStoreState",JSON.stringify(state))
})
5.8. 【store组合式写法】
Setup Store 也存在另一种定义 store 的可用语法。与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。在 Setup Store 中:
ref()
就是state
属性computed()
就是getters
function()
就是actions
Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。
实现一个音乐列表的功能(普通vue组件即可,此处需要组件销毁后数据还在)
文件列表如下
ts
import { defineStore } from "pinia";
import { reactive } from 'vue';
import axios from "axios";
export interface IComment {
name: string,
url: string,
picurl: string,
artistsname: string,
avatarurl: string,
nickname: string,
content: string
}
export const useMusicListStore = defineStore('music', () => {
//https://api.uomg.com/
//https://api.uomg.com/doc-comments.163.html
//https://api.uomg.com/api/comments.163?format=json
const tableData = reactive<IComment[]>([
{
"name": "满足",
"url": "http://music.163.com/song/media/outer/url?id=1313558538.mp3",
"picurl": "http://p2.music.126.net/rF6m9HfXqAZptLngigQXJg==/109951163668999283.jpg",
"artistsname": "肖战",
"avatarurl": "http://p1.music.126.net/g_JP6rzOkDYNCBXyPOVjZQ==/109951164407826586.jpg",
"nickname": "三七不是二十一JMQ",
"content": "记得有一次班上人谈论到你 坐第一排都听见的我很激动转过去说“嗯我知道我知道 那是我男朋友” 然后她们全都不说话看着我后面 我转过去 我们数学老师正认真的盯着我看 我们数学老师五十多啦还是个男老师 以为我真是谈恋爱啊~生日快乐 爱你 一直支持你"
},
{
"name": "红梅赞",
"url": "http://music.163.com/song/media/outer/url?id=1439975538.mp3",
"picurl": "http://p2.music.126.net/7kRz_B18IaWapEQPh3B7oA==/109951164898369103.jpg",
"artistsname": "肖战",
"avatarurl": "http://p1.music.126.net/voNbpHJ69sN9nNyIAJbZvQ==/109951164904265410.jpg",
"nickname": "绝甜空気-",
"content": "遇见美丽中国!希望中国青年都能坚持文化自信,延续梅兰竹菊的气节。支持中国新青年肖战,一起加油[爱心]"
}
])
async function load() {
let { data: { data } } = await axios.get('https://api.uomg.com/api/comments.163?format=json')
tableData.unshift(data)
}
return {tableData,load}
})
vue
<template>
<el-button type="primary" size="small" @click="load()">加载....</el-button>
<el-table :data="tableData" style="width: 100%" >
<el-table-column prop="name" label="乐曲名称" width="100" fixed/>
<el-table-column prop="artistsname" label="歌手" width="100" fixed/>
<el-table-column prop="nickname" label="评论昵称" width="190" />
<el-table-column prop="url" label="头像" width="90">
<template v-slot="scoped" >
<img :src="scoped.row.avatarurl" width="80px"/>
</template>
</el-table-column>
<el-table-column prop="content" label="评论内容" />
</el-table>
</template>
<script lang="ts" setup name="Music">
import { useMusicListStore } from "@/store/musicList";
import { storeToRefs } from "pinia";
const musicStore = useMusicListStore()
const {tableData} = storeToRefs(musicStore)
const load = musicStore.load
</script>
<style>
</style>
