Pinia与Vuex4的区别及【Pinia.js上手指南】
「2022 年什么会火?什么该学?本文正在参与“聊聊 2022 技术趋势”征文活动 」
认识Pinia
Pinia的优势
Pinia 相比 Vuex 更加简单,Pinia 是符合直觉的状态管理方式,让使用者回到了模块导入导出的原始状态,使状态的来源更加清晰可见
Vue-Devtools 支持:
- 一个追踪动作、变化的时间轴
- 出现在他们使用的组件中的存储Stores
- 时间旅行和更简便的调试
模块热替换:
- 在不要求重新加载页面条件下修改你的 stores
- 当开发时保持所有存在的状态(state)
插件:
- 使用插件扩展Pinia功能
为JS用户提供适当的 TypeScript
支持与自动完成
功能
支持服务端渲染
区别
Pinia API 与 Vuex 4有相当大的不同即:
- mutations 不再存在
- 不需要创建自定义的复杂包装器来支持TypeScript,一切都是类型化的,并且API的设计方式是尽可能利用TS类型推断。
- 不再有魔术字符串注入,导入函数,调用它们,享受自动完成
- 不需要动态添加
stores
,默认都是动态的,你甚至不会注意到。注意,你仍然可以随时手动使用stores
来注册它,但是因为它是自动的,所以不需要担心。 - 不再有 modules 的嵌套结构。你仍然可以通过在另一个
store
中导入和 using 一个store
来隐式嵌套stores
,但是Pinia提供了一种平面的设计结构,同时仍然支持store
之间的交叉组合方式。 - 没有
namespaced模块
。考虑到stores
的平面结构,namespacing stores
是定义store
的固有方式,你可以说所有的stores
都是namespacing
。
基本介绍:
1、创建defineStore()
我打算先去说一下defineStore()
,因为store是使用这个定义的,并且它需要一个唯一的名字,作为第一个参数传递:
import { defineStore } from 'pinia'
// useStore 一切例如 useUser, useCart
// 第一个参数是应用程序中store的唯一id
export const useStore = defineStore('main', {
// 其他选项...
})
2、使用store
我们 定义 一个 store
因为直到在setup()
内部调用“useStore()
”之前, store并不会被创建:
import { useStore } from '@/stores/counter'
export default {
setup() {
const store = useStore()
return {
// 您可以返回整个存储实例,以便在模板中使用它
store,
}
},
}
你可以定义任意多的stores
, 你应该在不同的文件中定义每个store
, 一旦存储被实例化,您就可以直接在存储上访在“state”、“getters”和“actions”中定义的任何属性。
注意:store
是一个用reactive
包装的对象
3、状态State
在 Pinia 中,状态
被定义为返回初始状态的函数
import { defineStore } from 'pinia'
const useStore = defineStore('storeId', {
// 推荐用于全类型推理的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断出它们的类型
counter: 0,
name: 'Eduardo',
isAdmin: true,
}
},
})
技巧:
如果您使用 Vue 2,您在 state
中创建的数据遵循与 data 在 Vue 实例中 相同的规则,即 状态对象必须是普通的,并且您需要在向其添加新属性Vue.set()时调用。
4、getters
类似于组件的computed
,用来封装计算属性 有缓存功能
getters: {
// 拥有缓存
// 函数接受一个可选参数:state 状态对象
// counts(state) {
// console.log('调用了');
// return state.count + 10
// }
// 如果再 getters中使用了 this 则必须手动指定返回值的类型 否则类型推到不出来
counts():number {
console.log('调用了');
return this.count + 10
}
}
打印缓存(下面完整使用列子有):
5、actions
封装业务逻辑,修改state
数据,actions可以是异步
的,你可以await
在它们内部进行任何 API 调用甚至其他操作,调用动作时,一切都会自动推断
actions: {
// 注意:不能使用箭头函数定义 action
// 为什么? y 很简单 因为箭头函数绑定外部的this,如果你使用箭头函数 指向就发送改变了
// 接收前面传过来的参数
ggState(num:number) {
// 简单情况这样修改即可
this.count+=num
this.foo = 'ggg'
this.arr.push(6)
// 如果复杂情况 (和前面一样,只不过拿到了这里)
// this.$patch({})
// this.$patch(state =>{})
}
}
注意
: 不能使用箭头函数定义 action
为什么? 很简单 因为箭头函数绑定外部的this,如果你使用箭头函数 指向就发送改变了
6、状态options API
如果您不使用 组合 API,而您正在使用computed
, methods
, …,则可以使用mapState()
帮助器将状态属性映射为只读计算属性:
import { mapState } from 'pinia'
export default {
computed: {
// 在组件内允许访问 this.counter
// 与从 store.counter 读取一样
...mapState(useStore, ['counter'])
// 与上面一样但是将注册它为 this.myOwnName
...mapState(useStore, {
myOwnName: 'counter',
// 你也可以写一个函数来访问 store
double: store => store.counter * 2,
// 它也能访问 `this` ,但是它不会正确地标注类型...
magicValue(store) {
return store.someGetter + this.counter + this.double
},
}),
},
}
Piani安装
pinia
使用您最喜欢的包管理器安装:
yarn add pinia
# or with npm
npm install pinia
创建一个 pinia 并将其传递给应用程序:
import { createPinia } from 'pinia'
app.use(createPinia())
如果您使用的是 Vue 2,您还需要安装一个插件并pinia
在应用程序的根目录注入创建的插件:
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)
const pinia = createPinia()
new Vue({
el: '#app',
// other options...
// ...
// note the same `pinia` instance can be used across multiple Vue apps on
// the same page
pinia,
})
注意:Vue3和Vue2使用起来是差不多的。
完整使用
项目结构:
首先,我们需要进行安装Pinaia
yarn add pinia
# or with npm
npm install pinia
然后再main.ts注册:
import { createApp } from 'vue'
import App from './App.vue'
import {createPinia } from 'pinia'
// 创建Pinia实列
const Pinia = createPinia()
const app = createApp(App)
// 挂载
app.use(Pinia)
createApp(App).mount('#app')
新建了一个store:
import { defineStore } from "pinia";
// 参数1:容器的ID,必须唯一,将来的Pinia 会把所有的容器挂载到跟容器上面
// 参数2:选项对象
// 返回参数:一个函数,调用得到容器实列
export const useMainStore = defineStore('main', {
/**
*
* @state 类似于组件的data,用来储存全局状态的
* 1、必须是函数:这样是为了在服务端渲染的时候,避免交叉请求导致数据状态污染
* 2、必须是箭头函数,这是为了更好的TS类型推导
*
*/
state: () => {
return {
count: 100,
foo: 'lc',
arr: [1, 2, 3]
}
},
/**
*
* @getters 类似于组件的computed,用来封装计算属性 有缓存功能
*
*/
getters: {
// 拥有缓存
// 函数接受一个可选参数:state 状态对象
// counts(state) {
// console.log('调用了');
// return state.count + 10
// }
// 如果再 getters中使用了 this 则必须手动指定返回值的类型 否则类型推到不出来
counts():number {
console.log('调用了');
return this.count + 10
}
},
/**
*
* @actions 封装业务逻辑,修改state数据
*
*/
//相当于组件中方法
// actions可以是异步的,你可以 await 在它们内部进行任何 API 调用甚至其他操作,调用动作(Actions)时,一切都会自动推断
actions: {
// 注意:不能使用箭头函数定义 action
// 为什么? 很简单 因为箭头函数绑定外部的this,如果你使用箭头函数 指向就发送改变了
// 接收前面传过来的参数
ggState(num:number) {
// 简单情况这样修改即可
this.count+=num
this.foo = 'ggg'
this.arr.push(6)
// 如果复杂情况 (和前面一样,只不过拿到了这里)
// this.$patch({})
// this.$patch(state =>{})
// patch 和 普通多次修改的区别再原理上面的区别是上面,按理来说多次修改也是批量提交的吧
// 普通多次修改:没改一次都需要进行一个视图更新
// patch 一次性把数据修改好
}
}
})
直接再组件中使用:
<template>
<div>
<p> {{mainStore.count}}</p>
<p> {{mainStore.foo}}</p>
<p> {{mainStore.counts}}</p>
<p> {{mainStore.counts}}</p>
<p> {{mainStore.counts}}</p>
</div>
<hr>
<p>
{{count}}
</p>
<p>
{{foo}}
</p>
<hr>
<p>
<button @click="handleChangState">修改数据</button>
</p>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { storeToRefs } from "pinia";
import { useMainStore } from "../store";
const mainStore = useMainStore();
console.log(mainStore.counts);
// 这是有问题的,因为这样拿到的数据不是响应式的,是一次性的。
// Pinia 其实就是把state 数据都做了 reactive 处理了
// const {count,foo} = mainStore
// 解决办法就是使用
// 把解构出来的数据做 ref 响应式代理
const { count, foo } = storeToRefs(mainStore);
const handleChangState = () => {
// 方式一:数据修改最简单的方式
// mainStore.count++;
// 方式二: 如果修改多个数据,建议使$patch 批量更新
// 这里传了一个对象
mainStore.$patch({
count: mainStore.count + 1,
foo: "gg",
});
// 方式三: $patch 一个函数,批量更新 (建议使用方式)
// 这里传了一个函数
// mainStore.$patch(state=>{
// state.count++
// state.foo = 'GG'
// state.arr.push(6)
// })
// 方法四:逻辑比较多的时候 可以封装到 actions 做处理
mainStore.ggState(5);
};
</script>
Vue-Devtools 支持:
与君共勉,感谢阅读!
转载自:https://juejin.cn/post/7056082767372615687