状态管理

@ Pinia
Vue3 采用 Pinia;Pinia 是 Vue 的专属状态管理库,使用大菠萝作为 logo
Vue2 采用 vuex
状态管理出现之前,使用共享组件完成对共享数据的使用;共享数据的修改通过共享组件发送自定义事件完成。这就意味着:任何一个使用它的组件都可以修改,不太容易维护;注意:共享数据和全局数据不是一回事
Overview
是一个保存全局状态 state 和业务逻辑的实体;可以在 整个应用 中访问的数据
利用仓库 store 集中式状态管理:抽取出组件间的共享状态,放在一个全局单例中来管理
不与组件树绑定,而承载着全局状态
仓库通常集中保存在项目的 stores 目录(应提前创建好),并按照功能创建 单独 的仓库文件
仓库在 onMounted 之前就已经准备好 - 兵马未动,粮草先行
更多信息,请访问 Pinia - setup-stores
Concepts
State - 驱动整个应用的数据源;是 store 的核心。应先定义能代表应用或反映应用状态的 state;相当于组件中的 data
Getter - 对状态的一种声明式映射;相当于组件中的 computed
Action - 根据用户的交互对数据源的操作;相当于组件中的 method
关系
Deployment
强烈建议在创建项目的时,选择状态管理;项目创建完毕后,可以直接使用;也可以单独安装,需要引入、配置等;不建议新手使用
在开发者视图中,使用慢速网络查看状态变化
1. Installation
npm i pinia --save
2. Import
在入口文件 main.js 引入并创建使用
完成后,在开发者工具中可以看到大菠萝
在 main.js 中引入并使用
import './assets/main.css'

import { createApp } from 'vue'
import App from './App.vue'
//1. 导入
import { createPinia } from 'pinia'
//2. 创建
const pinia = createPinia()
const app = createApp(App)
//3. 使用
app.use(pinia)
app.mount('#app')
Procedure

. 在默认示例的基础增加 msg

创建仓库文件 .js,使用语义化命名,如 counter.js
引入 defineStore 库及其它必要库
使用 defineStore 创建仓库实例,其中:

. 第一个参数 counter 是你的应用中 Store 的唯一 ID,Pinia 将用它来连接 store 和 devtools

. 第二个参数接收 option 对象或 setup 函数;setup 函数和组合式 API 类似,且功能更灵活

导出仓库示例,以 use 开头且以 Store 结尾命名,如 useCounterStore
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore  = defineStore('counter', ()=>{
  //state - ref()
  const count = ref(0)
  const msg = ref('hi,there.')
  
  //getters - computed()
  const doubleCount = computed( () => count.value * 2 )
  
  //action - function()
  const increment = () => count.value++

  return { count, msg, doubleCount, increment }
})
目标组件使用 Store

逻辑 - 引入仓库、创建仓库实例、使用仓库数据;创建仓库实例前无法使用

<script setup>
    import { useCounterStore } from '@/stores/counter';
    const store = useCounterStore();
    console.log('store', store);
</script>

结构 - 渲染仓库数据;这里采用内联事件

<template>
    <div>{{ store.count }}</div>
    <div>{{ store.msg }}</div>
    <div>{{ store.doubleCount }}</div>
    <div @click="store.increment">store handle</div>
</template>
Warning
直接 解构 使用 store 中的数据(方法)会丢失响应:当仓库状态变化时,count 不变,相当于是获取了仓库的一次性数据
let { count, increment } = counterStore;
<div>仓库数据 - {{ store.count }} - {{ count }}</div>
使用 storeToRefs 包保持响应式 - 不建议;命名冲突。。。可读性不好。。。运维困难。。。
import { storeToRefs } from 'pinia';
let { count, increment } = storeToRefs(counterStore);
Cases
一切皆仓库
[] 多组件共享
创建任意仓库,并创建一个状态 num
组件A中引入并使用;采用简单模板语法 {{num}}
组件B中引入并使用;采用双向绑定 v-mode
入口组件中引入并使用组件A、组件B,查看双向绑定时状态的共享变化
[] 页面导航仓库
创建仓库 - 商品 goods、状态 isLoading、请求 fetchGoods
请求商品数据 - 分别使用 fetch 和 axios
请求中,给出提示;请求结束,渲染数据
使用条件渲染追踪过程
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useGoodStore = defineStore('good', () => {
  const goods = ref([])
  const isLoading = ref(false)
  const fetchGoods = async () => {
    isLoading.value = true
    let res = await fetch('http://127.0.0.1:3000/goods')
    let data = await res.json()
    goods.value = data
    isLoading.value = false
  }
  return { goods, isLoading, fetchGoods, }
})
import { onMounted } from 'vue';
import { useGoodStore } from '@/stores/good';
const store = useGoodStore();
onMounted(() => {
  store.fetchGoods();
})
<div v-if="store.isLoading">    loading    </div>
<div v-else>
    //
</div>
[] 商品仓库
[] 购物车仓库
使用仓库并更新仓库
[] 模态框仓库
侦听仓库变化显示提示信息
商品仓库和购物车仓库都有数据加载。可以通过侦听仓库的 isLoading 给前端相应的提示
msg 可以提供给模态框使用
watch(() => goodStore.isLoading, (newVal, oldVal) => {
    if (newVal) {
        msg.value = '正在加载中...'
    } else {
        msg.value = '加载完成'
    }
    show.value = true;
})
[] 汉堡菜单仓库
项目从桌面端切换到移动端时,菜单折叠,由汉堡按钮控制折叠和展开;单击菜单项时,折叠菜单
1. 创建仓库 - 仓库参考代码 menu.js
import { ref } from 'vue'
import { defineStore } from 'pinia'

export const useMenuStore = defineStore('menu', () => {
  const flag = ref(false)
  const switchFlag = () => {
    flag.value ? flag.value = false : flag.value = true
  }

  return { flag, switchFlag }
})
2. 在使用仓库的组件结构中,引入仓库,通过汉堡按钮的单击,由标记 flag 控制动态类的应用,达到菜单的折叠和展开
改进:单击菜单项,折叠菜单
提示:在路由配置中,引入仓库,在导航前置守卫中实例化仓库并切换标记switchFlag;请查看 路由 - 导航守卫
[] 主题切换仓库

主题切换/换肤/日夜模式

通过为 html 指定自定义属性实现 CSS 变量的更新达到切换效果

主题样式 - 这里仅给出需要变化的 CSS 变量,其它变量可以在额外的 :root 单独定义
:root {
    --txt-color: #303133;
    --bg-color: #f1f1f1;
    --shadow-color: rgba(0, 0, 0, 0.1);
}

// 类形式
.data-theme {
    --txt-color: #f1f1f1;
    --bg-color: #131313;
    --shadow-color: rgba(255, 255, 255, 0.1);
}

// 属性形式
[data-theme="dark"] {
    --txt-color: #f1f1f1;
    --bg-color: #131313;
    --shadow-color: rgba(255, 255, 255, 0.1);
}
主题仓库
import { defineStore } from 'pinia'
import { ref, onMounted } from 'vue'
export const useThemeStore = defineStore('theme', () => {

  const isDarkMode = ref(false);

  const toggleDarkMode = () => {
    isDarkMode.value = !isDarkMode.value;
    document.documentElement.setAttribute('data-theme', isDarkMode.value ? 'dark' : 'light');
  };

  return { isDarkMode, toggleDarkMode }
})
在SFC中引入仓库,并使用相应的图标展示暗黑和白天,最后绑定主题切换事件
import { useThemeStore } from '@/stores/theme';
const themeStore = useThemeStore();
<div class="theme-icon" @click="themeStore.toggleDarkMode">
    <span v-if="themeStore.isDarkMode" class="iconfont icon-night-mode-fill"></span>
    <span v-else class="iconfont icon-daytime-mode-fill"></span>
</div>
[] 访问量统计仓库
持久化存储
导航守卫