Vuex 还是 Pinia ?小孩子才做选择!

发布于:2024-04-27 ⋅ 阅读:(26) ⋅ 点赞:(0)

image.png

如果你在项目中频繁遇到状态管理,却不知使用哪种工具,这篇文章就能解决你的选择困难症。不管是 Vuex 还是 Pinia ,都是 Vue 开发的常用状态管理工具,针对于它们的优缺点,以及不同之处,本文将详细展开介绍!

1. 介绍

什么是 Store ?

Store 是一个保存状态和业务逻辑的实体,它并不与你的组件树绑定。换句话说,它承载着全局状态。它有点像一个永远存在的组件,每个组件都可以读取和写入它。

Vuex

就是一个专为 Vue 应用程序开发的 Store。它用于管理 Vue 应用中的共享状态,使得多个组件能够方便地访问和修改相同的数据。Vuex 的核心概念包含statemutationsactionsgetters 等。

Pinia

是一个专为 Vue 3 设计的 Store。它是在 Vue 3 响应式 API 的基础上构建的,旨在提供一种轻量、灵活且直观的状态管理解决方案。与传统的 Vuex 不同,Pinia 不依赖于全局对象,而是通过创建独立的 store 实例来管理状态。stategetter 和 action 是 Pinia 的三个重要概念。

2. 安装

Vuex

yarn add vuex@next --save
# 或者使用 npm
npm install vuex@next --save

Pinia

yarn add pinia
# 或者使用 npm
npm install pinia

注意:实际项目开发可不要即用 Vuex 又用 Pinia,除非公司是你家开的。

3. 创建与使用

Vuex

在自己项目的src目录下创建一个store目录,再创建一个index.js,在这里我们创建 Vuex 的store实例。

import { createStore } from 'vuex'

const store = createStore({
    //需要管理的数据存放在这里
    state() {
        return {
            msg: "hello vuex",
        };
    },

    //唯一可以同步修改state的地方
    mutations: {
    },

    //异步修改state,本质还是通过mutations修改
    actions: {
    },

    //类似于vue中的计算属性computed
    getters: {
    },

    //如果需要vuex管理的数据多的话,可以拆分为一个个模块
    modules: {
    }
})

export default store;

main.js 中引入刚刚创建的实例:

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

createApp(App).use(store).mount('#app')

App.vue中使用:

<template>
  <div>
     {{store.state.msg}}
  </div>
</template>

<script setup>
import { useStore } from 'vuex'
let store = useStore()
</script>

如果页面成功显示hello vuex,恭喜你,已经成功创建了一个Vuex Store实例。

Pinia

同理,在src目录下创建一个store目录,在index.js中创建 Pinia 的store实例:

import { defineStore  } from 'pinia'

export const store = defineStore('store',{
    state: ()=>{
        return {
            msg:'hello pinia',
        }
    },
    getters: {},
    actions: {}
})

main.js中引入创建的 Pinia 实例:

import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const pinia = createPinia()

createApp(App).use(pinia).mount('#app')

App.vue中使用:

<template>
  <div>
     {{storeA.msg}}
  </div>
</template>

<script setup>
import { store } from './store';
let storeA = store()
</script>

如果页面成功显示hello pinia,恭喜你 Pinia 实例创建成功~

4. 修改状态

Vuex

在组件中直接修改

<template>
  <div>
    {{store.state.msg}}
  </div>
</template>

<script setup>
import { useStore } from 'vuex'
let store = useStore()
store.state.msg = 'hello juejin' //直接赋值修改
</script>

方法可行,但是这样直接修改状态会绕过 Vuex 的 mutation 操作,破坏了单向数据流的概念。Vuex 还是推荐通过mutations来修改状态,以确保状态的变化是可追踪的。

在mutations中修改

import { createStore } from 'vuex'

const store = createStore({
    //需要管理的数据存放在这里
    state() {
        return {
            msg: "hello vuex",
        };
    },

    //唯一可以同步修改state的地方
    mutations: {
        changeMsg(state,data){
            state.msg = data
        }
    },
    ......
})

export default store;

在组件中用commit触发状态变更:

<template>
  <div>
    {{store.state.msg}}
  </div>
</template>

<script setup>
import { useStore } from 'vuex'
let store = useStore()
store.commit('changeMsg','hello juejin')//commit触发状态变更
</script>

actions中进行提交mutations进行修改

import { createStore } from 'vuex'

const store = createStore({
    state() {
        return {
            msg: "hello vuex",
        };
    },

    mutations: {
        changeMsg(state, data) {
            state.msg = data
        }
    },

    //异步通过mutations修改state
    actions: {
        async getMsg({ commit }, newMsg) {
            setTimeout(() => {
                commit('changeMsg', newMsg);
            }, 1000);
        }
    },
    ......
})

export default store;

在组件中使用dispatch进行分发actions

<template>
  <div>
    {{store.state.msg}}
    <!-- hello juejin -->
  </div>
</template>

<script setup>
import { useStore } from 'vuex'
let store = useStore()
store.dispatch('getMsg','hello juejin')//dispatch分发
</script>

这里我们在actions中设置了一个一秒的定时器,来模拟异步操作,使用一进入页面,显示的还是hello vuex,但一秒后就变成hello juejin了。

下图是vuex的数据流程,简单描述一下:就是组件通过调用 dispatch 触发一个 Action,Action 的处理函数执行一些异步操作,然后提交一个 Mutation,Mutation 的处理函数修改 State,State 的变化触发视图的更新。

image.png

Pinia

在组件中直接修改

<template>
  <div>
    {{storeA.msg}}
  </div>
</template>

<script setup>
import { store } from './store';
let storeA = store()
storeA.msg = 'hello juejin'
console.log(storeA.msg);
</script>

使用$patch方法

使用$patch方法可以修改一个或多个状态

import { defineStore  } from 'pinia'

export const store = defineStore('store',{
    state: ()=>{
        return {
            msg:'hello pinia',
            name:'yangyangyang'
        }
    },
    getters: {},
    actions: {}
})

在组件中进行修改

<template>
  <div>
    {{storeA.msg}}
  </div>
</template>

<script setup>
import { store } from './store';
let storeA = store()
console.log(storeA.msg,storeA.name);
storeA.$patch({
  msg:'hello juejin',
  name:'miemiemie'
})
console.log(storeA.msg,storeA.name);
</script>

image.png

在actions中进行修改

与 Vuex 的actions不同,Pinia中的actions既可以是同步也可以是异步,由于Pinia 中没有mutations,所以工作都交给了actions

import { defineStore  } from 'pinia'

export const store = defineStore('store',{
    state: ()=>{
        return {
            msg:'hello pinia',
            name:'yangyangyang'
        }
    },
    actions: {
        changeMsg(data){
            this.msg = data
        }
    },
    getters: {},
})

直接在组件中调用changeMsg方法,而不用像 Vuex 一样dispatch进行分发。

<template>
  <div>
    {{storeA.msg}}
  </div>
</template>

<script setup>
import { store } from './store';
let storeA = store()
storeA.changeMsg('hello juejin')
</script>

重置 state

使用选项式 API时,可以通过调用 store 的 $reset() 方法将 state 重置为初始值。

<script setup>
import { store } from './store';
let storeA = store()
storeA.changeMsg('hello juejin')
console.log(storeA.msg);
storeA.$reset()
console.log(storeA.msg);
</script>
image.png

5. 模块化

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,对 Vuex 或 Pinia 进行模块化开发至关重要,尤其是对于大型项目。

Vuex

Vuex 允许我们将 Store 拆分成多个模块(module),每个模块都有自己的 State、Mutation、Action 和 Getter。

一般项目开发中,我们都会将每个module单独存放在一个文件中,然后再引入总入口store/index.js

在src目录下创建一个 modules 文件夹,然后在其中创建你的模块文件。

模块A

//modules/moduleA.js
const moduleA = {
  state: () => ({ 
     msg:'hello moduleA'
  }),
  mutations: {},
  actions: {},
  getters: {}
}
export default moduleA

模块B

//modules/moduleB.js
const moduleB = {
  state: () => ({ 
     msg:'hello moduleB'
  }),
  mutations: {},
  actions: {},
  getters: {}
}
export default moduleB

将各模块引入主模块

//store/index.js
import { createStore } from 'vuex';
import moduleA from '../modules/moduleA';
import moduleB from '../modules/moduleB';
const store = createStore({
  modules: {
    moduleA,
    moduleB
  }
})
export default store;

在组件中使用moduleAmoduleB

<template>
  <div>
    {{store.state.moduleA.msg}}
    <br>
    {{store.state.moduleB.msg}}
  </div>
</template>

<script setup>
import { useStore } from 'vuex'
let store = useStore()
</script>

image.png

为了防止各模块中 mutations 或者 actions 中的方法重名引发的问题,modules提供了命名空间 的方法(namespaced: true

以moduleA为例:

//modules/moduleA.js
const moduleA = {
    namespaced: true,
    state: () => ({ 
       msg:'hello moduleA'
    }),
    mutations: {
       changeMsg(state,data){
          state.msg = data
       }
    },
    actions: {},
    getters: {}
  }
export default moduleA

为了避免其他模块中也有相同命名的changeMsg方法,我们可以通过 “模块名/方法名” 的方式调用。

import { useStore } from 'vuex'
let store = useStore()
console.log(store.state.moduleA.msg);
store.commit('moduleA/changeMsg','hello juejin')
console.log(store.state.moduleA.msg);
image.png

Pinia

Pinia 每个状态库本身就是一个模块。Pinia 没有modules,如果想使用多个Store,直接定义多个 Store 传入不同的 ID (defineStore()的第一个参数)即可。

import { defineStore  } from 'pinia'

export const useModuleA = defineStore('storeA',{
  state: () => (),
  actions: {},
  getters: {}
});

export const useModuleB = defineStore('storeB',{
  state: () => (),
  actions: {},
  getters: {}
});

在组件中,要使用哪个模块就引入哪个模块。

import { useModuleA } from './store';
let storeA = useModuleA()
console.log(storeA.msg);
storeA.changeMsg('hello juejin')
console.log(storeA.msg);
image.png

最后

如果你的项目是基于 Vue 2,可以选择 Vuex,如果你的项目基于Vue 3,喜欢使用组合式 API,使用 TS ,那么更推荐使用 Pinia。当然,以上仅供参考,具体根据个人和团队的具体情况来选择。

已将学习代码上传至 ,欢迎大家学习指正!

技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 “点赞 收藏+关注” ,感谢支持!!