19 Vue3 状态管理 Pinia
Pinia 简介
Pinia (发音为 /piːnjʌ/,类似英文中的“peenya”) 是最接近有效包名 piña (西班牙语中的 pineapple,即“菠萝”) 的词。菠萝花实际上是一组各自独立的花朵,它们结合在一起,由此形成一个多重的水果。与 Store 类似,每一个都是独立诞生的,但最终它们都是相互联系的。它 (菠萝) 也是一种原产于南美洲的美味热带水果。
什么是 Pinia
Pinia 是 Vue 的专属的最新状态管理库 ,是 Vuex 状态管理工具的替代品
- 提供更加简单的 APl(去掉了 mutation)
- 提供符合组合式风格的 APl(和 Vue3 新语法统一)
- 去掉了 modules 的概念,每一个 store 都是一个独立的模块
- 配合 TypeScript 更加友好,提供可靠的类型推断
为什么使用 Pinia
Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉组合式 API 的话,你可能会认为可以通过一行简单的 export const state = reactive({}) 来共享一个全局状态。对于单页应用来说确实可以,但如果应用在服务器端渲染,这可能会使你的应用暴露出一些安全漏洞。而如果使用 Pinia,即使在小型单页应用中,你也可以获得如下功能:
- Devtools 支持
- 追踪 actions、mutations 的时间线
- 在组件中展示它们所用到的 Store
- 让调试更容易的 Time travel
- 热更新
- 不必重载页面即可修改 Store
- 开发时可保持当前的 State
- 插件:可通过插件扩展 Pinia 功能
- 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。
- 支持服务端渲染
对比 Vuex
Vuex 3.x 只适配 Vue 2,而 Vuex 4.x 是适配 Vue 3 的。
Pinia API 与 Vuex(<=4) 也有很多不同,即:
- mutation 已被弃用。它们经常被认为是极其冗余的。它们初衷是带来 devtools 的集成方案,但这已不再是一个问题了。
- 无需要创建自定义的复杂包装器来支持 TypeScript,一切都可标注类型,API 的设计方式是尽可能地利用 TS 类型推理。
- 无过多的魔法字符串注入,只需要导入函数并调用它们,然后享受自动补全的乐趣就好。
- 无需要动态添加 Store,它们默认都是动态的,甚至你可能都不会注意到这点。注意,你仍然可以在任何时候手动使用一个 Store 来注册它,但因为它是自动的,所以你不需要担心它。
- 不再有嵌套结构的模块。你仍然可以通过导入和使用另一个 Store 来隐含地嵌套 stores 空间。虽然 Pinia 从设计上提供的是一个扁平的结构,但仍然能够在 Store 之间进行交叉组合。你甚至可以让 Stores 有循环依赖关系。
- 不再有可命名的模块。考虑到 Store 的扁平架构,Store 的命名取决于它们的定义方式,你甚至可以说所有 Store 都应该命名。
关于如何将现有 Vuex(<=4) 的项目转化为使用 Pinia 的更多详细说明,请参阅 Vuex 迁移指南。
Pinia 使用
安装
pnpm add pinia以后项目中可以直接使用 pnpm create vue@latest 在项目初始化时自动添加。
创建 pinia 实例
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
// 创建 Pinia 实例
const pinia = createPinia()
// 创建根实例
const app = createApp(App)
// 使用 Pinia 实例
app.use(pinia)
// 挂载 app 视图
app.mount('#app')定义 Store
创建一个
store文件夹,在里面创建一个xxx.js文件。store 文件名一般和 id 相同。Store 是用 defineStore() 定义的,它的第一个参数要求是一个独一无二的 ID。Pinia 将用它来连接 store 和 devtools。为了养成习惯性的用法,将返回的函数命名为 use... 是一个符合组合式函数风格的约定。
defineStore() 的第二个参数可接受两类值:Setup 函数或 Option 对象。
js// store/alerts.js import { defineStore } from 'pinia' // 你可以对 `defineStore()` 的返回值进行任意命名,但最好使用 store 的名字,同时以 `use` 开头且以 `Store` 结尾。(比如 `useUserStore`,`useCartStore`,`useProductStore`) // 第一个参数是你的应用中 Store 的唯一 ID。 export const useAlertsStore = defineStore('alerts', { // 其他配置... })你可以定义任意多的 store,但为了让使用 pinia 的益处最大化 (比如允许构建工具自动进行代码分割以及 TypeScript 推断),你应该在不同的文件中去定义 store。
Option Store
与 Vue 的选项式 API 类似,我们也可以传入一个带有 state、actions 与 getters 属性的 Option 对象。
js// store/counter.js export const useCounterStore = defineStore('counter', { state: () => ({ count: 0 }), getters: { double: (state) => state.count * 2, }, actions: { increment() { this.count++ }, }, })你可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而 actions 则是方法 (methods)。
为方便上手使用,Option Store 应尽可能直观简单。
Setup Store
与 Vue 组合式 API 的 setup 函数 相似,我们可以传入一个函数,该函数定义了一些响应式属性和方法,并且返回一个带有我们想暴露出去的属性和方法的对象。
jsexport const useCounterStore = defineStore('counter', () => { // 数据 state const count = ref(0) // 计算属性 getters const doubleCount = computed(() => count.value * 2) // 修改数据的方法 actions function increment() { count.value++ } // 以对象形式返回 return { count, doubleCount, increment } })在 Setup Store 中:
ref()就是state属性computed()就是gettersfunction()就是actions
Setup store 比 Option Store 带来了更多的灵活性,因为你可以在一个 store 内创建侦听器,并自由地使用任何组合式函数。不过,请记住,使用组合式函数会让 SSR 变得更加复杂。
组件使用 Store
<script setup>
// 引入 useCounterStore 函数
import { useCounterStore } from '@/stores/counter'
// 通过 useCounterStore() 函数来获取 store 实例
// 可以在组件中的任意位置访问 `store` 变量 ✨
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>
<template>
<div>
<h1>{{ name }}</h1>
<p>Count: {{ store.count }}</p>
<p>Double: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
</div>
</template>- 一旦 store 被实例化,你可以直接访问在 store 的
state、getters和actions中定义的任何属性。 - 请注意,
store是一个用reactive包装的对象,这意味着不需要在 getters 后面写.value,就像setup中的props一样,如果你写了,我们也不能解构它。
案例 计数器
- 定义 Store(state + action)
- 组件使用 Store
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// 数据 state
const count = ref(0)
// 修改数据的方法 actions
const autoIncrement = () => {
const timer = setInterval(() => {
count.value++
if (count.value >= 20)
clearInterval(timer)
}, 300)
}
// 以对象形式返回
return { count, autoIncrement }
})<script setup>
// 引入 useCounterStore 函数
import { useCounterStore } from '@/store/counter.js'
// 通过 useCounterStore() 函数来获取 store 实例
const counterStore = useCounterStore()
</script>
<template>
<button @click="counterStore.autoIncrement">
Auto Increment
</button>
<p>{{ counterStore.count }}</p>
</template>State 状态
- 在大多数情况下,state 都是你的 store 的核心。人们通常会先定义能代表他们 APP 的 state。
- 在 Pinia 中,state 被定义为一个返回初始状态的函数。这使得 Pinia 可以同时支持服务端和客户端。
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const name = ref('Zhang san')
const array = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
const hasMore = ref(true)
const autoIncrement = () => {
const timer = setInterval(() => {
count.value++
if (count.value >= 20)
clearInterval(timer)
}, 300)
}
const $reset = () => count.value = 0
return { count, name, array, hasMore, autoIncrement, $reset }
})<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/store/counter.js'
const counterStore = useCounterStore()
console.log(counterStore)
console.log('counterStore.count', counterStore.count)
console.log('counterStore.name', counterStore.name)
console.log('counterStore.array', counterStore.array)
console.log('counterStore.hasMore', counterStore.hasMore)
const { autoIncrement, $reset } = counterStore
const { count, name, array, hasMore } = storeToRefs(counterStore)
</script>
<template>
<button @click="autoIncrement">
Auto Increment
</button>
<button @click="$reset">
Reset Count
</button>
<p> Count: {{ count }} </p>
<p> Name: {{ name }} </p>
<p> Array: {{ array }} </p>
<p> Has More: {{ hasMore }} </p>
</template>Getter 计算属性
- Pinia 中的 getters 直接使用 computed 函数 进行模拟,组件中需要使用需要把 getters return 出去
- Getter 完全等同于 store 的 state 的计算值。可以通过
defineStore()中的getters属性来定义它们。推荐使用箭头函数,并且它将接收state作为第一个参数。 - 大多数时候,getter 仅依赖 state,不过,有时它们也可能会使用其他 getter。因此,即使在使用常规函数定义 getter 时,我们也可以通过
this访问到整个 store 实例,但 (在 TypeScript 中) 必须定义返回类型。这是为了避免 TypeScript 的已知缺陷,不过这不影响用箭头函数定义的 getter,也不会影响不使用this的 getter。 - Getter 只是幕后的计算属性,所以不可以向它们传递任何参数。不过,你可以从 getter 返回一个函数,该函数可以接受任意参数。
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const array = ref([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
const autoIncrement = () => {
const timer = setInterval(() => {
count.value++
if (count.value === 20)
clearInterval(timer)
}, 300)
}
const evenOrOdd = computed(() => (count.value % 2 === 0 ? 'even' : 'odd'))
const evenArray = computed(() => array.value.filter(n => n % 2 === 0))
const autoAddToArray = () => {
const timer = setInterval(() => {
array.value.push(array.value.length + 1)
if (array.value.length === 20)
clearInterval(timer)
}, 300)
}
const $reset = () => {
count.value = 0
array.value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
return { count, array, autoIncrement, evenOrOdd, evenArray, autoAddToArray, $reset }
})<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/store/counter.js'
const counterStore = useCounterStore()
console.log(counterStore)
const { autoAddToArray, $reset } = counterStore
const { count, array, evenOrOdd, evenArray } = storeToRefs(counterStore)
</script>
<template>
<p>count: {{ count }} | {{ evenOrOdd }}</p>
<p>array: {{ array }}</p>
<p>evenArray: {{ evenArray }}</p>
<button @click="counterStore.autoIncrement">
Auto Increment
</button>
<button @click="autoAddToArray">
autoAddToArray
</button>
<button @click="$reset">
reset
</button>
</template>Action 异步操作
- 异步 action 函数的写法和组件中获取异步数据的写法完全一致
- Action 相当于组件中的 method。它们可以通过
defineStore()中的actions属性来定义,并且它们也是定义业务逻辑的完美选择。 - 类似 getter,action 也可通过
this访问整个 store 实例,并支持完整的类型标注 (以及自动补全 ✨)。不同的是,action可以是异步的,你可以在它们里面await调用任何 API,以及其他 action!
- 案例需求:在 Pinia 中获取频道列表数据并把数据渲染 App 组件的模板中
- 接口地址:
http://geek.itheima.net/v1_0/channels
import axios from 'axios'
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useChannelStore = defineStore('channel', () => {
const channels = ref([])
const fetchChannels = async () => {
const { data } = await axios.get('http://geek.itheima.net/v1_0/channels')
channels.value = data.data.channels
}
return { channels, fetchChannels }
})<script setup>
import { useChannelStore } from '@/store/channel.js'
const channelStore = useChannelStore()
console.log(channelStore)
</script>
<template>
<el-card class="box-card">
<template #header>
<div class="card-header">
<h3>频道列表</h3>
<el-button round size="large" type="primary" @click="channelStore.fetchChannels">
获取频道列表数据
</el-button>
</div>
</template>
<div v-if="channelStore.channels.length">
<div v-for="item in channelStore.channels" :key="item.id" class="text item">
{{ `${item.id} - ${item.name}` }}
</div>
</div>
<div v-else class="text item">
0 - xxx
</div>
</el-card>
</template>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.text {
font-size: 14px;
}
.item {
margin-bottom: 18px;
}
.box-card {
width: 480px;
}
</style>storeToRefs 工具函数
- 创建一个引用对象,包含 store 的所有 state、getter 和 plugin 添加的 state 属性。
- 类似于
toRefs(),但专门为 Pinia store 设计,所以 method 和非响应式属性会被完全忽略。 - storeToRefs 会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性。非响应式数据和 action 可以直接解构。
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore('counter', () => {
// 响应式数据
const count = ref(0)
// 普通数据
const name = 'Zhang san'
// 方法 action
const increment = () => count.value++
const autoIncrement = () => {
const timer = setInterval(() => {
count.value++
if (count.value >= 20)
clearInterval(timer)
}, 300)
}
return { count, name, increment, autoIncrement }
})<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/store/counter.js'
const counterStore = useCounterStore()
console.log(counterStore)
// 响应式数据通过 storeToRefs 函数转换后可以结构出来,同时保持数据的响应式
const { count } = storeToRefs(counterStore)
// 非响应式数据和 Action(方法)可以直接结构出来
const { name, increment, autoIncrement } = counterStore
</script>
<template>
<p> Counter: {{ count }} </p>
<p> Name: {{ name }} </p>
<button @click="increment">
Increment
</button>
<button @click="autoIncrement">
Auto Increment
</button>
</template>Pinia 持久化插件
pinia-plugin-persistedstate 介绍
persistedstate 丰富的功能可以使 Pinia Store 的持久化更易配置:
- 与
vuex-persistedstate相似的 API - 所有 Store 均可单独配置
- 自定义 storage 和数据序列化
- 恢复持久化数据前后的 hook
- 每个 Store 具有丰富的配置
- 兼容 Vue 2 和 3
- 无任何外部依赖
插件使用
安装插件 pinia-plugin-persistedstate。
bashpnpm add pinia-plugin-persistedstate将插件添加到 pinia 实例上。
js// main.js import { createApp } from 'vue' import { createPinia } from 'pinia' // 导入持久化的插件 import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import App from './App.vue' // 创建 Pinia 实例 const pinia = createPinia() // 创建根实例 const app = createApp(App) // pinia 插件的安装配置 app.use(pinia.use(piniaPluginPersistedstate)) // 视图的挂载 app.mount('#app')Store 持久化配置。
jsimport { defineStore } from 'pinia' export const useStore = defineStore( 'main', () => { const someState = ref('你好 pinia') return { someState } }, { persist: true, }, )
默认配置
- 使用
localStorage进行存储 store.$id作为 storage 默认的 key- 使用
JSON.stringify/JSON.parse进行序列化/反序列化 - 整个 state 默认将被持久化
常用自定义配置
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
state: () => ({
someState: '你好 pinia',
}),
persist: {
// 在这里进行自定义配置
},
})key:自定义存储的 key。默认为store.$id。Key 用于引用 storage 中的数据。jsimport { defineStore } from 'pinia' export const useStore = defineStore('store', { state: () => ({ someState: '你好 pinia', }), persist: { key: 'my-custom-key', }, })这个 Store 将被持久化存储在
localStorage中的my-custom-keykey 中。storage:自定义存储方式。默认为localStorage。jsimport { defineStore } from 'pinia' export const useStore = defineStore('store', { state: () => ({ someState: '你好 pinia', }), persist: { storage: sessionStorage, }, })这个 store 将被持久化存储在
sessionStorage中。path:自定义存储路径。默认为整个 state。jsimport { defineStore } from 'pinia' export const useStore = defineStore('store', { state: () => ({ save: { me: 'saved', notMe: 'not-saved', }, saveMeToo: 'saved', }), persist: { paths: ['save.me', 'saveMeToo'], }, })这个 store 中只有
save.me和saveMeToo被持久化,而save.notMe不会被持久化。
全局持久化配置
在安装插件之后,你可以使用 createPersistedState 来初始化插件。这些配置将会成为项目内所有 Store 的默认选项。
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(
createPersistedState({
storage: sessionStorage,
}),
)每个声明 persist: true 的 Store 都会默认将数据持久化到 sessionStorage 中。
全局 key 配置
全局 key 配置接受传入 Store key 的函数,并返回一个新的 storage 密钥。
// main.js
import { createPinia } from 'pinia'
import { createPersistedState } from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(
createPersistedState({
key: (id) => `__persisted__${id}`,
}),
)// store.js
import { defineStore } from 'pinia'
defineStore('store', {
state: () => ({ saved: '' }),
persist: true,
})- store 将保存在
__persisted__storekey 下,而不是store下。 - 当你需要在全局范围内对所有 Store key 添加前缀/后缀时,应考虑此选项。
示例代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { ElButton, ElCard, ElRadio, ElRadioButton, ElRadioGroup, ElTable, ElTableColumn, ElText } from 'element-plus'
// 导入持久化的插件
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import App from './App.vue'
// 创建 Pinia 实例
const pinia = createPinia()
// 创建根实例
const app = createApp(App)
// 使用 Pinia 实例
app.use(pinia)
app.use(ElCard)
app.use(ElText)
app.use(ElButton)
app.use(ElTable)
app.use(ElTableColumn)
app.use(ElRadioGroup)
app.use(ElRadio)
app.use(ElRadioButton)
// pinia 插件的安装配置
app.use(pinia.use(piniaPluginPersistedstate))
// 挂载 app 视图
app.mount('#app')import axios from 'axios'
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useChannelStore = defineStore(
'channel',
() => {
const channels = ref([])
const fetchChannels = async () => {
const { data } = await axios.get('http://geek.itheima.net/v1_0/channels')
channels.value = data.data.channels
}
return { channels, fetchChannels }
},
{
// 使用默认配置
persist: true,
},
)import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCounterStore = defineStore(
'counter',
() => {
const count = ref(0)
const name = ref('Zhang san')
// 方法 action
const increment = () => count.value++
const changeName = newName => name.value = newName
const autoIncrement = () => {
const timer = setInterval(() => {
count.value++
if (count.value >= 20)
clearInterval(timer)
}, 300)
}
return { count, name, increment, changeName, autoIncrement }
},
{
// pinia-plugin-persistedstate 插件配置
persist: {
// key: 自定义存储的 key
key: 'pinia-persistedstate-counter',
// storage: 存储方式,默认 localStorage
storage: localStorage,
// paths: 需要持久化的数据
paths: ['count'],
},
},
)import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useStudentStore = defineStore(
'student',
() => {
const studentList = [
{ id: 1, name: '赵博', age: 16, sex: '男', class: '高三二班' },
{ id: 2, name: '李丽丽', age: 14, sex: '女', class: '高三一班' },
{ id: 3, name: '武婷', age: 17, sex: '女', class: '高一二班' },
{ id: 4, name: '高强', age: 18, sex: '女', class: '高三一班' },
{ id: 5, name: '王桂珍', age: 14, sex: '男', class: '高三一班' },
{ id: 6, name: '叶娜', age: 17, sex: '女', class: '高二一班' },
{ id: 7, name: '陈海燕', age: 17, sex: '男', class: '高三一班' },
{ id: 8, name: '张凤兰', age: 14, sex: '女', class: '高三二班' },
{ id: 9, name: '蔡成', age: 15, sex: '女', class: '高三一班' },
{ id: 10, name: '敖萍', age: 18, sex: '女', class: '高三二班' },
]
const addStudent = ([
{ id: 11, name: '许鹏', age: 14, sex: '男', class: '高三一班' },
{ id: 12, name: '李杨', age: 14, sex: '男', class: '高三二班' },
{ id: 13, name: '华莹', age: 18, sex: '女', class: '高三一班' },
{ id: 14, name: '何兰英', age: 17, sex: '女', class: '高三二班' },
{ id: 15, name: '乔亮', age: 15, sex: '男', class: '高二一班' },
{ id: 16, name: '傅帆', age: 16, sex: '女', class: '高三一班' },
{ id: 17, name: '李杨', age: 16, sex: '女', class: '高三一班' },
{ id: 18, name: '陈霞', age: 18, sex: '女', class: '高一一班' },
{ id: 19, name: '柳琴', age: 16, sex: '女', class: '高三一班' },
{ id: 20, name: '王明', age: 14, sex: '男', class: '高三一班' },
{ id: 21, name: '冯龙', age: 15, sex: '女', class: '高一一班' },
{ id: 22, name: '郭勇', age: 15, sex: '男', class: '高一一班' },
{ id: 23, name: '余燕', age: 17, sex: '女', class: '高三一班' },
{ id: 24, name: '范琳', age: 15, sex: '男', class: '高三一班' },
{ id: 25, name: '吕慧', age: 14, sex: '女', class: '高一一班' },
{ id: 26, name: '赵桂珍', age: 15, sex: '男', class: '高三一班' },
])
const students = ref(studentList)
const addStudentAction = () => students.value.push(...addStudent)
const resetStudentAction = () => students.value = studentList
const logStudentList = () => console.log(students.value)
const DeleteStudentAction = student => students.value = students.value.filter(item => item.id !== student.id)
return { students, addStudentAction, resetStudentAction, logStudentList, DeleteStudentAction }
},
{
persist: {
key: 'pinia-student',
paths: ['students'],
},
},
)<script setup>
import { storeToRefs } from 'pinia'
import { ref } from 'vue'
import { useChannelStore } from '@/store/channel.js'
import { useCounterStore } from '@/store/counter.js'
import { useStudentStore } from '@/store/student.js'
const channelStore = useChannelStore()
const { channels } = storeToRefs(channelStore)
const { fetchChannels } = channelStore
const counterStore = useCounterStore()
const { count, name } = storeToRefs(counterStore)
const { increment, changeName, autoIncrement } = counterStore
const studentStore = useStudentStore()
const { students } = storeToRefs(studentStore)
const { addStudentAction, resetStudentAction, logStudentList, DeleteStudentAction } = studentStore
const tableLayout = ref('fixed')
</script>
<template>
<p> Counter: {{ count }} </p>
<p> Name: {{ name }} </p>
<el-button type="primary" @click="increment">
Increment
</el-button>
<el-button type="info" @click="autoIncrement">
Auto Increment
</el-button>
<el-button plain type="info" @click="changeName('Li si')">
Change Name
</el-button>
<p>
<label> Input Name: <input v-model="name" @change="changeName"> </label>
</p>
<hr>
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>频道列表</span>
<el-button round size="large" type="primary" @click="fetchChannels">
获取频道列表数据
</el-button>
</div>
</template>
<div v-if="channels.length > 0">
<el-text v-for="item in channels" :key="item.id" class="mx-1" size="large">
{{ `${item.name}` }}
</el-text>
</div>
<div v-else class="text item">
频道列表数据
</div>
</el-card>
<hr>
<el-radio-group v-model="tableLayout">
<el-radio-button label="fixed" />
<el-radio-button label="auto" />
</el-radio-group>
<el-table
:data="students" :default-sort="{ prop: 'id', order: 'descending' }" :table-layout="tableLayout" border
height="250" stripe style="width: 100%"
>
<el-table-column label="Id" prop="id" sortable />
<el-table-column label="Name" prop="name" sortable />
<el-table-column label="Age" prop="age" sortable />
<el-table-column label="Sex" prop="sex" sortable />
<el-table-column label="Class" prop="class" />
<el-table-column label="Operations">
<template #default="scope">
<el-button type="danger" @click="DeleteStudentAction(scope.row)">
Delete
</el-button>
</template>
</el-table-column>
</el-table>
<div style="margin-top: 20px">
<el-button plain type="primary" @click.once="addStudentAction">
添加学生
</el-button>
<el-button type="success" @click="resetStudentAction">
重置学生数据
</el-button>
<el-button type="danger" @click="logStudentList">
打印学生数据
</el-button>
</div>
</template>
<style scoped>
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.text {
font-size: 14px;
}
.item {
margin-bottom: 18px;
}
.box-card {
width: 480px;
}
</style>总结
Pinia 是用来做什么的?
新一代的状态管理工具,替代 vuex
Pinia 中还需要 mutation 吗?
不需要,action 既支持同步也支持异步
Pinia 如何实现 getter?
computed 计算属性函数
Pinia 产生的 Store 如何解构赋值数据保持响应式?
storeToRefs 工具函数
Pinia 如何快速实现持久化?
pinia-plugin-persistedstate插件