vuex & pinia 核心

88 阅读3分钟

Vuex

概述

1、是什么?

vuex是 一个vue提供的 状态管理工具插件,状态就是数据。用来管理vue通用的数据(多组件共享的数据)

2、场景

  • 某个状态很多个组件 中使用(例如:个人信息)
  • 多个组件 共同维护 一份数据(例如:购物车)

3、优势

  • 共同维护一份数据,数据集中化管理
  • 响应式变化
  • 操作简洁(vuex提供了一些辅助函数)

创建仓库

目标:安装vuex插件,初始化一个空仓库

image.png

1、安装

npm install vuex@3
或者
yarn add vuex@3

2、新建 store/index.js 专门存放vuex

image.png 3、创建仓库

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'

Vue.use(Vuex)

// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context('./modules', true, /\.js$/)

// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  // set './app.js' => 'app'
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
  const value = modulesFiles(modulePath)
  modules[moduleName] = value.default
  return modules
}, {})

const store = new Vuex.Store({
  modules,
  getters
})

export default store

4、在 main.js 中导入挂载到 vue实例上。

import store from './store'
new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

通过 this.$store访问

image.png

核心

1、state 状态

目标:明确如何给仓库提供数据,如何使用仓库的数据

  • 提供数据

State提供唯一的公共数据源,所有共享的数据都要统一放到 Store中的state中存储。

state对象中可以添加我们要共享的数据

// 创建仓库
const store = new Vuex.Strore({
    strict: true, // 通过 strict 可以开启严格模式
    
    // state 状态,即数据,类似于 vue 组件中的 data
    // 区别
    // 1、data 是组件自己的数据
    // 2、state 是所有组件共享的数据
    state: {
        title: '仓库大标题'
        count: 1
    }
})
  • 使用数据
获取store
(1`this.$store`
 (2) `import` 导入 `store`
 
 模板中使用: {{ $store.state.xxx }}
 组件逻辑中: this.$store.state.xxx
 js模块中: store.state.xxx
  • 同构辅助函数(简化代码)

    • mapState是辅助函数,帮助我们把 store 中的数据 自动映射 到 组件的计算属性中
    (1)导入 `mapState`
    import { mapState } from 'vuex'
    
    (2)数组方式引入`state`
    mapState(['count'])
    
    (3)展开运算符映射
    computed: {
        ...mapState(['count'])
    }
    

image.png

2、mutations

目标:明确 vuex 同样遵循单向数据流,组件中不能直接修改仓库的数据。

掌握 mutations 的操作流程,来修改 state 数据。

state数据的修改只能通过 mutations, mutations必须是同步的,便于监测数据变化,记录调试

(1) 定义 `mutations` 对象,对象中存放修改 `state` 的方法
const store = new Vuex.Store({
    state: {
        count: 0
    },
    
    // 定义mutations
    mutations: {
        // 第一个参数是当前 store 的 state 属性
        addCount (state) {
            state.count += 1
        }
    }
})

(2)组件中提交调用 `mutations`
this.$store.commit('addCount')
  • 提供 mutations 函数, 带参数
mutations: {
    // 参数n 的位置 只能有一个,如果需要传多个参数,将参数包装成对象的形式传递
    addCount (state, n) {
        state.count += n;
    }
}
  • 页面中提交调用 mutations
this.$store.commit('addCount', 10)
  • 辅助函数mapMutations

说明:把位于 mutations中的方法提取出来,映射到组件methods

mutations: {
    subCount (state, n) {
        state.count -= n;
    }
}


// 使用 mapMutations
import { mapMutations } from 'vuex';

methods: {
    ...mapMutations(['subCount'])
}
// 不使用 mapMutations,就需要通过 commit触发
methods: {
    subCount(n) {
        this.$store.commit('subCount', n);
    }
}

// 调用
this.subCount(10)

3、actions

目标:明确 actions 的基本语法,处理异步操作。

需求:一秒后,修改 state 的 count 为 666

mutations: {
    changeCount(state, newCount) {
        state.count = newCount;
    }
},

// 提供 actions 方法
actions: {
    setAsyncCount (context, num) {
        // 一秒后,给一个数, 去修改 num
        setTimeout(() => {
            context.commit('changeCount', num)
        }, 1000)
    }
}

// 页面中 通过 dispatch 调用
this.$store.dispatch('setAsyncCount', 666)
  • 辅助函数 mapActions

说明:mapActions 是把位于 actions中的方法 提取出来,映射到 组件methods

actions: {
    setAsyncCount (context, num) {
        // 一秒后,给一个数, 去修改 num
        setTimeout(() => {
            context.commit('changeCount', num)
        }, 1000)
    }
}

// 使用 mapActions
import { mapActions } from 'vuex'

methods: {
    ...mapActions(['setAsyncCount'])
}

// 不使用 mapActions,需要通过 dispatch 触发
methods: {
    setAsyncCount(n) {
        this.$store.dispatch('setAsyncCount', n)
    }
}

// 调用
this.setAsyncCount(666)

getters

说明:除了state之外,有时我们需要从state派生出一些状态,这些状态是依赖 state 的,此时会用到getters(类似于计算属性)

例如:state 中定义了 list, 为 1-10 的数组,组件中,需要显示所有大于5的数据

state: {
    list: [1,2,3,4,5,6,7,8,9,10]
},

// 1、定义getters
getters: {
    // 注意:
    // 1、getters 函数的第一个参数是 state
    // 2、getters 函数必须有返回值
    filterList (state) {
        return state.list.filter(item => item > 5)
    }
}

// 2、访问 getters
(1)通过 store 访问 getters
{{ $store.getters.filterList }}

(2)通过辅助函数 mapGetters 映射
computed: {
    ...mapGetters(['filterList'])
}

// 3、使用
{{ filterList }}

模块 module(进阶语法)

由于 vuex 使用 单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store对象就有可能变得相当臃肿。(当项目变得越来越大的时候,vuex会变得越来越难以维护)。 image.png

  • 模块拆分
const state = {
    userInfo: {
        name: '张三',
        age: 18
    }
}

// 模块拆分
// user 模块: store/modules/user.js
export default {
    namespaced: true, // 开启命名空间
    state,
    mutations,
    actions,
    getters
}

// 导入模块
import user from './modules/user'

const store = new Vuex.Store({
    modules: {
        user
    }
})

尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的state中,属性名就是模块名需要开启命名空间,才会挂载到子模块。

  • 使用模块中的数据

    • 直接通过模块名访问 $store.state.模块名.xxx

    • 通过 mapState 映射

      • 默认根级别的映射 mapState(['xxx'])
      • 子模块的映射 mapState('模块名', ['xxx'])

      注意: 子模块的映射 需要开启命名空间 namespaced: true

  • 使用模块中 getters 中的数据

    • 直接通过模块名访问 $store.getters['模块名/xxx']

    • 通过 mapGetters 映射

      • 默认根级别的映射mapGetters(['xxx'])
      • 子模块的映射 mapGetters('模块名', ['xxx'])

      注意: 子模块的映射 需要开启命名空间 namespaced: true

  • 调用子模块中的mutations

    • 直接通过 store调用 $store.commit('模块名/xxx', 额外参数)

    • 通过 mapMutations映射

      • 默认根级别的映射mapMutations(['xxx'])
      • 子模块的映射 mapMutations('模块名', ['xxx'])

      注意: 子模块的映射 需要开启命名空间 namespaced: true

  • 调用子模块中的actions

    • 直接通过 store调用 $store.dispatch('模块名/xxx', 额外参数)

    • 通过 mapActions映射

      • 默认根级别的映射mapActions(['xxx'])
      • 子模块的映射 mapActions('模块名', ['xxx'])

      注意: 子模块的映射 需要开启命名空间 namespaced: true

Pinia

什么是Pinia?

Piniavue的最新状态管理工具,是 vuex替代品

  • 提供更加简单的 API(去掉了mutations),action 既支持同步,也支持异步
  • 提供符合,组合式风格的 API(和 vue3 新语法统一)
  • 去掉了 modules的概念,每一个 store都是一个独立的模块
  • 配合 typescript 更加友好,提供可靠的类型推断

创建引用

// main.js
import { createApp } from 'vue';
import App from './App.vue';

import './assets/main.css';

// 1、导入createPinia
import { createPinia } from 'pinia';

// 2、执行方法得到实例
const pinia = createPinia();

// 3、把pinia实例加入到app应用中
createApp(App).use(pinia).mount('#app');

基础使用

Store

1、定义store

通过defineStore定义Store,它要求第一个参数是独一无二的名字,这个名字也被用作id,是必须传入的。defineStore的第二个参数可以接受两种类值:setup函数Option对象

// stores/counter.js
// 定义 store (state + action)
import { defineStore } from 'pinia'
import { ref } from 'vue'

// 组合式写法
export const userCounterStore = difineStore('counter', () => {
    // 数据(state)
    const count = ref(0)
    
    // 修改数据的方法 (action 支持同步+异步)
    const increment = () => {
        count.value++
    }
    
    // 以对象形式返回
    return { count, increment }
})

2、组件中使用 Store

<script setup>
    // 1、导入 userCounterStore 方法
    import { userCounterStore } from '@/stores/counter'
    
    // 2、执行方法 等到 counterStore 对象
    const counterStore = useCounterStore()
</script>

<template>
    <button @click="counterStore.increment">{{ counterStore.count }}</button>
</template>

getters实现

pinia中的getters直接使用computed函数 进行模拟。

// stores/counter.js
// 定义 store (state + action)
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const userCounterStore = difineStore('counter', () => {
    // 数据(state)
    const count = ref(0)
    
    // 修改数据的方法 (action 支持同步+异步)
    const increment = () => {
        count.value++
    }
    
    // getter定义
    const doubleCount = computed(() => count.value * 2)
    
    // 以对象形式返回
    return { count, increment, doubleCount }
})

action如何实现异步

action中实现异步和组件中定义数据和方法的风格完全一致。

// stores/counter.js
// 定义 store (state + action)
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import axios from 'axios';

export const userCounterStore = difineStore('counter', () => {
    // 数据(state)
    const count = ref(0)
    
    // 修改数据的方法 (action 支持同步+异步)
    const increment = () => {
        count.value++
    }
    
    // getter定义
    const doubleCount = computed(() => count.value * 2)
    
    // 定义异步action
    const list = ref([]);
    const getList = async () => {
        const res = await axios.get('请求api');
        list.value = res.data.data;
    }
    
    // 以对象形式返回
    return { count, increment, doubleCount, list, getList }
})

storeToRefs工具函数

使用storeToRefs函数可以辅助保持数据(state + getter)的响应式结构

// 响应式丢失,视图不再更新
const { count, doubleCount } = counterStore

// 保持数据响应式
import { storeToRefs } from 'pinia';

// 数据相关处理(保持响应式更新)
const { count, doubleCount } = storeToRefs(counterStore)
// 方法直接从原来的 counterStore 中解构赋值
const { increment } = counterStore;

$subscribe:订阅

/**** userStore.vue文件 ******/ 
import { defineStore } from "pinia";

export const useUserStore = defineStore('user', {
  // 存储数据
  state() {
    return {
      userName: '张三',
      enName: 'zhangsan',
      age: 18
    }
  },

  // 方法: 用于响应组件中的 动作
  actions: {
    increment() {
      // 修改数据 (this 是当前store)
      this.age += 1;
    }
  },

  getters: {
    bigAge: state => state.age * 10,

    upperName():string {
      return this.enName.toUpperCase()
    }
  }
})


/**** index.vue文件 ******/ 
<template>
  <div>首页</div>
  全局用户信息:<br />
  姓名:{{ userStore.userName }}, 年龄:{{ age }}, 放大十倍: {{ userStore.bigAge }}
  英文:{{ userStore.upperName }}
  <br />
  <button @click="change">修改store</button>
  <button @click="change1">修改store —— storeToRefs</button>
</template>

<script setup lang="ts" name="home">
import { useUserStore } from '@/store/user';
import { storeToRefs } from 'pinia'

// 读取 store 数据
const userStore = useUserStore();

// storeToRefs 只会关注store中的数据,不会对方法进行 ref包裹
let { age } = storeToRefs(userStore);

// 
userStore.$subscribe((mutate, state) => {
  console.log('userStore里面保存的数据发生变化了',mutate, state)
})

const change1 = () => {
  age.value += 1
}

const change = () => {

  // 第一种修改 store 的方法
  // userStore.age += 1;


  // 第二种修改store 的方法
  // userStore.$patch({
  //   age: 20,
  //   userName: '李四'
  // })


  // 第三种 修改store 的方式: action
  userStore.increment();
}
</script>

Pinia持久化插件

  • 安装插件pinia-plugin-persistedstate
npm i pinia-plugin-persistedstate
  • main.js使用
import persist from 'pinia-plugin-persistedstate'

app.use(createPinia().use(persist))
  • store仓库中,persist: true开启