vue3 基础-Pinia 可能替代 Vuex 的全局数据状态管理
Pinia 初体验
Pinia.js是由Vue.js团队核心成员开发的新一代状态管理器,使用Composition Api进行重新设计的,也被视为下一代Vuex。
Pinia是一个Vue的状态管理库,允许跨组件、跨页面进行全局共享状态,也由于其设计的简洁性、和对typescript的良好支持,取代Vuex指日可待。
或许,你在想在vue3中Composition API完全可以使用响应式变量进行全局共享状态,为什么还需要Pinia呢?其实你忽略的一点,你这样做在单页面进行应用是完全可以的,但是如果页面时服务端进行渲染呈现可能就有问题了.
安装
npm install pinia
引入到项目
在 main.js 中只需要调用createPinia()
方法将pinia
实例化,然后挂载到vue实例
上就可以了.
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')
下一步就应该去创建我们的数据仓库store
了,store
中存储的是我们需要共享的数据.
创建store
需要调用pinia
中的defineStore
方法,该方法接受两个参数,第一个参数是store
的唯一 id
,第二个参数是store
的配置属性.
defineStore
方法配置store属性时,有两种写法,一种是Option
对象形式,一种是Setup
函数形式。
对于 Option
的方式和 Vuex 是一样的, 区别在于, 取消了 Mutations
而让 Actions
同时能进行同步和异步的操作, 这很方便.
在 src 目录下创建一个 store / index.js 的文件:
import { defineStore } from 'pinia'
// 模块1
export const useStore = defineStore("store", {
state: () => ({
name: 'youge',
age: 18
}),
// 同步 / 异步 都可以写在一起, 这比 Veux 方便多了
actions: {
changeName(name) {
this.name = name
},
aysncChangeName(name) {
setTimeout(() => {
this.name = name
}, 2000)
}
}
})
// 模块2
// export const useStore2 = defineStore("store2", { })
我其实还是更偏向这种 vuex 风格写法的.
当然用 setup
的写法也支持的.
import { ref } from 'vue'
import { defineStore } from 'pinia'
// 模块1
export const useStore = defineStore('store2', () => {
// 原来的 state 数据直接进行定义即可, 主要要响应式
let name = ref('youge')
let age = ref(18)
// 同步/异步任务, 直接定义成普通函数即可
const changeName = (newName) => name.value = newName
function aysncChangeName(newName) {
setTimeout(() => {
name.value = newName
}, 2000);
}
// 划重点! 这里一定要 return 出去哦
return { name, changeName, aysncChangeName }
})
// 模块2
// export const useStore2 = defineStore("store2", () => { })
则对应的视图, 这里以 setup 的风格:
<template>
<div>
<p>同步获取 store 的 name 为: {{ name }}</p>
<p>异步获取 store 的 name 为: {{ name }}</p>
</div>
<button @click="changeName('jack')">同步修改数据</button>
<button @click="aysncChangeName('jack')">异步修改数据</button>
</template>
<script setup>
// import Father from './Father.vue'
import { useStore } from './store'
import { storeToRefs } from 'pinia'
const store = useStore()
// store 的方法, 直接解构使用就行
const { changeName, aysncChangeName } = store
// store 的变量就需要包装一下成响应式了
const { name } = storeToRefs(store)
</script>
如果用 setup 的写法呢, 需注意变量结构会丢失响应式, 需要用 pinia 提供的 storeToRefs
包装一下即可.
<template>
<!-- <div>
<Father />
</div> -->
<div>
<p>同步获取 store 的 name 为: {{ name }}</p>
<p>异步获取 store 的 name 为: {{ name }}</p>
</div>
<button @click="changeName('jack')">同步修改数据</button>
<button @click="aysncChangeName('jack')">异步修改数据</button>
<button @click="handleClick('jack')">直接修改数据</button>
</template>
<script setup>
// import Father from './Father.vue'
import { useStore } from './store'
import { storeToRefs } from 'pinia'
const store = useStore()
const { changeName, aysncChangeName } = store
// store 的变量就需要包装一下成响应式了
const { name } = storeToRefs(store)
const handleClick = (newName) => store.name = newName
</script>
或者直接用对象.属性的方式, 不通过结构也行的, 这样也不会丢失响应式.
<template>
<!-- <div>
<Father />
</div> -->
<div>
<p>同步获取 store 的 name 为: {{ store.name }}</p>
<p>异步获取 store 的 name 为: {{ store.name }}</p>
</div>
<!-- 1. 同步 / 异步 修改 -->
<button @click="store.changeName('jack')">同步修改数据</button>
<button @click="store.aysncChangeName('jack')">异步修改数据</button>
<!-- 2. 直接在标签上改 -->
<button @click="handleClick('jack')">直接修改数据</button>
</template>
<script setup>
import { useStore } from './store'
const store = useStore()
const handleClick = (newName) => store.name = newName
</script>
子组件调用触发的 getActivePinia( ) 问题
原因是 pinia
在 main.js
中还没有注册好, 便已在其他文件 (组件) 中使用了它, 就会报错了, 方案就是提前注册呀.
首先, 在 App.vue
同级目录下新建一个名为 "pinia.js"
的文件, 当然文件名和位置都是可以任意的, 这里只是建议.
// src/pinix.js
import { createPinia } from "pinia"
const pinia = createPinia()
export default pinia
然后, 在 main.js 进行引用注册 pinia.
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import pinia from './pinia'
const app = createApp(App)
app.use(pinia)
app.mount('#app')
最后, 在需要用到全局 store 的地方分别进行引入 即可.
如父组件 App.vue
<template>
<div>
<p>同步获取 store 的 name 为: {{ store.name }}</p>
<p>异步获取 store 的 name 为: {{ store.name }}</p>
</div>
<!-- 1. 同步 / 异步 修改 -->
<button @click="store.changeName('jack')">同步修改数据</button>
<button @click="store.aysncChangeName('jack')">异步修改数据</button>
<!-- 2. 直接在标签修改 -->
<button @click="handleClick('jack')">直接修改数据</button>
<p>
子组件:
<Child />
</p>
</template>
<script setup>
// import Father from './Father.vue'
import Child from './Child.vue'
import { useStore } from './store'
import pinia from './pinia'
const store = useStore(pinia)
const handleClick = (newName) => store.name = newName
</script>
里面的子组件 Child.vue
, 注意在 script 标签中一定要加上 setup
<template>
<div>
数据: {{ store.name }}
</div>
</template>
<script setup>
import { useStore } from './store'
import pinia from './pinia'
const store = useStore(pinia)
</script>
页面:
同步获取 store 的 name 为: youge
异步获取 store 的 name 为: youge
(同步修改数据) (异步修改数据) (直接修改数据)
子组件:
数据: youge
这真的是, 用 Pinia 感觉就和没有用一样丝滑.
不过这里也只是初步体验, 具体使用还是待考察中.