三 变得优雅
1 storeToRefs
toRefs 用于解构普通 reactive 对象保持响应性,而 storeToRefs 是 Pinia 专用工具,专为解构 Store 的 state 设计(只关注数据,不会将方法ref包裹),二者适用场景不同,推荐按需选用以确保响应性。

① Count.vue
ts
<template>
<div class="count">
<h2>开始计算:{{sum}}</h2>
<h3>{{nikename}}出了新时装{{fashion}}</h3>
<select v-model.number="n">
<!-- 或用:value -->
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">+</button>
<button @click="subtract">-</button>
</div>
</template>
<script setup lang="ts" name="Count">
import {ref,reactive,toRefs} from "vue";
// 引入 storeToRefs
import {storeToRefs} from "pinia";
// 引入刚才的仓库
import {useCountStore} from '@/store/count';
const countStore = useCountStore()
let n = ref(1) // 选择的数字
const {sum,nikename,fashion} = toRefs(countStore)
console.log('@@@',toRefs(countStore))
console.log('!!!',storeToRefs(countStore))
// 方法
function add(){
// 方法3
countStore.increment(n.value)
}
function subtract(){
countStore.sum -= n.value
}
</script>
<style scoped>
.count {
font-family: "游明朝", "Yu Mincho", serif;
background: #f5e8d0; /* 米色背景 */
border: 2px solid #8b0000; /* 深红色边框 */
border-radius: 8px;
padding: 1rem;
box-shadow: 4px 4px 0 #5c3317; /* 复古阴影 */
text-align: center;
}
.count h2 {
color: #8b0000; /* 深红色标题 */
font-size: 1.5rem;
text-shadow: 1px 1px 0 #f5e8d0; /* 轻微描边增强可读性 */
margin: 0;
padding: 0.5rem;
border-bottom: 1px dashed #5c3317; /* 虚线分隔线 */
}
select, button {
font-family: "游明朝", serif; /* 昭和风字体 */
background: #f5e8d0; /* 米色背景 */
color: #8b0000; /* 深红文字 */
border: 1px solid #5c3317; /* 深棕边框 */
padding: 0.4rem 0.8rem;
box-shadow: 2px 2px #5c3317; /* 复古阴影(省略模糊半径) */
cursor: pointer;
}
select:hover, button:hover {
background: #e8d5b5; /* 悬停加深背景 */
}
button:active {
box-shadow: 1px 1px #5c3317; /* 点击时阴影收缩 */
transform: translateY(1px); /* 轻微下移 */
}
</style>
② RapTalk.vue
ts
<template>
<div class="talk">
<button @click="getRapTalk">用最潮的方式来表达爱</button>
<ul>
<li v-for="talk in talkList" :key="talk.id">{{ talk.title }}</li>
</ul>
</div>
</template>
<script setup lang="ts" name="RapTalk">
import {storeToRefs} from "pinia";
import {useTalkStore} from '@/store/rapTalk';
const talkStore = useTalkStore()
const {talkList} = storeToRefs(talkStore);
// 方法
async function getRapTalk(){
talkStore.getTalk()
}
</script>
<style scoped>
.talk {
font-family: 'Yomogi', cursive;
background: #f9f0e1;
border: 1px solid #8b0000;
padding: 12px;
}
button {
background: #8b0000;
color: white;
border: none;
padding: 4px 12px;
cursor: pointer;
}
li {
margin: 8px 0;
padding: 8px;
background: #fffaf0;
border-left: 2px solid #d2b48c;
}
</style>
2 getters
① 介绍
getters 是 计算属性(Computed Properties),用于从 state 中派生出新的数据或逻辑。它的核心作用是 封装复杂计算、提高代码复用性、保持响应式更新,类似于 Vue 组件中的 computed,但作用域是 Store 级别(跨组件共享)。
② count.ts
ts
import {defineStore} from "pinia";
export const useCountStore = defineStore('count',{
// 搭配方法3使用,存储函数用以响应组件中的动作
actions:{
// 增加动作 可引入逻辑判断
increment(value:number){
// 修改数据
console.log('@@@',this.sum)
if (this.sum < 3366){
this.sum += value
}
}
},
state(){
return{
sum:888,
nikename:'雕刻家',
fashion:'笼中偶'
}
},
getters:{
// 类似计算属性
bestSum:state => state.sum * 10,
// bestSum(state){
// return state.sum * 10
// },
//
upFashion():string{
return this.fashion.concat('PLUS')
}
}
})
③ Count.vue
ts
<template>
<div class="count">
<h2>开始计算:{{sum}}*10后:{{bestSum}}</h2>
<h3>{{nikename}}出了新时装{{fashion}}升级{{upFashion}}</h3>
<select v-model.number="n">
<!-- 或用:value -->
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">+</button>
<button @click="subtract">-</button>
</div>
</template>
<script setup lang="ts" name="Count">
import {ref,reactive,toRefs} from "vue";
// 引入 storeToRefs
import {storeToRefs} from "pinia";
// 引入刚才的仓库
import {useCountStore} from '@/store/count';
const countStore = useCountStore()
let n = ref(1) // 选择的数字
// 取用 bestSum,upFashion
const {sum,nikename,fashion,bestSum,upFashion} = toRefs(countStore)
console.log('@@@',toRefs(countStore))
console.log('!!!',storeToRefs(countStore))
// 方法
function add(){
// 方法3
countStore.increment(n.value)
}
function subtract(){
countStore.sum -= n.value
}
</script>
<style scoped>
.count {
font-family: "游明朝", "Yu Mincho", serif;
background: #f5e8d0; /* 米色背景 */
border: 2px solid #8b0000; /* 深红色边框 */
border-radius: 8px;
padding: 1rem;
box-shadow: 4px 4px 0 #5c3317; /* 复古阴影 */
text-align: center;
}
.count h2 {
color: #8b0000; /* 深红色标题 */
font-size: 1.5rem;
text-shadow: 1px 1px 0 #f5e8d0; /* 轻微描边增强可读性 */
margin: 0;
padding: 0.5rem;
border-bottom: 1px dashed #5c3317; /* 虚线分隔线 */
}
select, button {
font-family: "游明朝", serif; /* 昭和风字体 */
background: #f5e8d0; /* 米色背景 */
color: #8b0000; /* 深红文字 */
border: 1px solid #5c3317; /* 深棕边框 */
padding: 0.4rem 0.8rem;
box-shadow: 2px 2px #5c3317; /* 复古阴影(省略模糊半径) */
cursor: pointer;
}
select:hover, button:hover {
background: #e8d5b5; /* 悬停加深背景 */
}
button:active {
box-shadow: 1px 1px #5c3317; /* 点击时阴影收缩 */
transform: translateY(1px); /* 轻微下移 */
}
</style>

3 $subscribe
① 介绍
$subscribe() 是 Store 实例提供的一个方法,用于 监听整个 Store 的 state 变化,并在变化时触发回调函数。它的核心作用是 响应式地跟踪状态变更,类似于 Vue 的 watch(),但更专注于 Store 的全局状态管理。
② rapTalk.ts
从 localStorage 获取 'talkList' 数据时,需通过 JSON.parse 解析字符串并处理可能的 null 或解析错误
ts
import {defineStore} from "pinia";
import axios from "axios";
import {nanoid} from "nanoid";
export const useTalkStore = defineStore('talk',{
actions:{
async getTalk(){
let {data:{content:title}} = await axios.get('https://v1.jinrishici.com/rensheng.json');
let obj = {id:nanoid(),title}
this.talkList.unshift(obj)
}
},
state(){
return{
// 获取本地存储内容,注意存入的字符串需转回来才可用
// 通过断言强制将返回值视为 string 类型。但存在风险:如果 'talkList' 不存在或不是字符串(如 null),后续解析会报错。
// 提供默认值 [](空数组)以防解析失败或数据不存在。
talkList:JSON.parse(localStorage.getItem('talkList') as string)||[]
}
}
})
③ RapTalk.vue
可将数组内容存入本地存储,实现刷新不丢失。
ts
<template>
<div class="talk">
<button @click="getRapTalk">用最潮的方式来表达爱</button>
<ul>
<li v-for="talk in talkList" :key="talk.id">{{ talk.title }}</li>
</ul>
</div>
</template>
<script setup lang="ts" name="RapTalk">
import {storeToRefs} from "pinia";
import {useTalkStore} from '@/store/rapTalk';
const talkStore = useTalkStore()
const {talkList} = storeToRefs(talkStore);
// 类似 watch 参数为:本次修改信息mutate 数据state
talkStore.$subscribe((mutate,state)=>{
console.log('talkStore内数据变化了',mutate,state)
// 将talkList数组内容作为字符串形式存入本地
localStorage.setItem('talkList',JSON.stringify(state.talkList))
})
// 方法
async function getRapTalk(){
talkStore.getTalk()
}
</script>
<style scoped>
.talk {
font-family: 'Yomogi', cursive;
background: #f9f0e1;
border: 1px solid #8b0000;
padding: 12px;
}
button {
background: #8b0000;
color: white;
border: none;
padding: 4px 12px;
cursor: pointer;
}
li {
margin: 8px 0;
padding: 8px;
background: #fffaf0;
border-left: 2px solid #d2b48c;
}
</style>


删除本地存储可重新添加歌词

4 store组合式写法
① 介绍:将选项式写法改为组合式写法,RapTalk.vue不需改动
② rapTalk.ts
ts
import {defineStore} from "pinia";
import axios from "axios";
import {nanoid} from "nanoid";
// 引入 reactive
import {reactive} from "vue";
// 选项式改组合式写法 参数二 改为函数
export const useTalkStore = defineStore('talk',()=>{
// talkList => state
const talkList=reactive(
JSON.parse(localStorage.getItem('talkList') as string)||[]
)
// getTalk => actions
async function getTalk(){
let {data:{content:title}} = await axios.get('https://v1.jinrishici.com/rensheng.json');
let obj = {id:nanoid(),title}
talkList.unshift(obj)
}
// 组合式要注意返回
return {talkList,getTalk}
})
