pinia简述
参考文献:
官方文档
spa
spa回顾
概念:就是只有一张Web页面的应用。单页面程序SPA是加载单个HTML页面并在用户与应用程序交互时动态更新页面的Web应用程序
spa引入pinia
npm install pinia
//main.js或main.ts中
import { createApp } from 'vue'
import {createPinia} from "pinia"
createApp(App).use(new createPinia()).mount('#app')
引言
在vue3中,其实最简单、最简洁的状态管理不是vuex,也不是pinia,是单个reactive函数导出
import { ref } from"vue";
export const state = ref(false)
这样就在不同组件进行共同使用状态,并且具有响应式的效果(同理,对象可以用reactiva进行包裹)
但是这样会让程序陷入安全漏洞
基本使用
创建相关文件夹store,其中有文件index进行状导出
整体目录树结构如下:
index.js文件中:
import { defineStore } from "pinia";
//定义一个简单的状态,action就是其中内部封装的方法,有利于操控store
//注意,这样直接使用的Store具有响应式
export const useCountStore=defineStore("couter",{
state:()=>{
return {count:0}
},
actions:{
//表示增加的操作,状态值进行改变:
imcrement(){
this.count++
},
//表示改变数量的操作,状态值也同样会改变:
changeCount(count){
this.count=count;
}
}
}
App.vue文件中的script层
//在.vue文件中渲染
<script setup>
import {useCountStore} from './stores/index'
const counter=useCountStore()
function changeState(){
//第一种:通过store中的函数改变store中的值
// counter.imcrement()
//第二种:直接++改变store中的值
counter.count++
counter.changeCount(100)
}
const count=counter.count
</script>
<template>
<!--注意:当元素没有结构赋值前-->
<div>this is fater compoment:{{counter.count}}</div>
<button @click="changeState">改变状态</button>
</template>
上一组案例只是最简单的
下面来一个比较充分的应用:
1.查看state的值:state中可以有各种类型的值,对象,字符、数值,对象同样也可以是各种类型组成
2. getter不是一个方法,是属性,使用的时候调用方法,返回相关的值,为了避免直接操作this,方法会传入此时的状态state,但是,函数中都能操作state和this,让this发生响应式的改变
3.
export const todos = defineStore('todos', {
state: () => ({
/** @type {{ text: string, id: number, isFinished: boolean }[]} */
todos: [],
/** @type {'all' | 'finished' | 'unfinished'} */
filter: 'all',
// type 会自动推断为 number
nextId: 0,
}),
getters: {
//操作state,不用this改变状态
//getter不是一个方法,是属性,使用的时候调用方法,返回相关的值
//本质是内部计算值
finishedTodos(state) {
// 自动完成! ✨
以下两种都会改变用户状态
//this.filter="666"
state.filter="666"
return state.todos.filter((todo) => todo.isFinished)
},
unfinishedTodos(state) {
return state.todos.filter((todo) => !todo.isFinished)
},
/**
* @returns {{ text: string, id: number, isFinished: boolean }[]}
*/
//getter方法相互调用
filteredTodos(state) {
if (this.filter === 'finished') {
// 自动调用其他 getter ✨
return this.finishedTodos
} else if (this.filter === 'unfinished') {
return this.unfinishedTodos
}
return this.todos
},
},
actions: {
// 任何数量的参数,返回一个 Promise 或者不返回
//这里和gettes就有区别了
//getter是返回了目标对象或者值
//提供一个函数封装状态,
addTodo(text) {
// 你可以直接改变状态
this.todos.push({ text, id: this.nextId++, isFinished: false })
},
},
})
调用相关方法后返回的值,影响用户状态
State状态(类比data)
对象
const counter=useCountStore()
经过这一步得到的counter是一个proxy对象,其中的count属性就是一个ObjRefImpl对象
基本操作
const store = useStore()
//访问
store.counter++
//重置
store.$reset()
//修改
//A计划
//不推荐这一种,因为这一种还需要让代码内部自己判断有哪里进行了更新
store.$patch({
counter: store.counter + 1,
name: 'Abalam',
})
//B计划
//往状态里面进行针对性修改
cartStore.$patch((state) => {
state.items.push({ name: 'shoes', quantity: 1 })
state.hasChanged = true
})
//替换state
store.$state = { counter: 666, name: 'Paimon' }
App.vue中进行展示
<template>
<div>this is fater compoment:{{double}},{{magicValue}}</div>
<button @click="changeState">改变状态</button>
</template>
代码中的this
代码中的store
响应式
store 是一个用reactive 包裹的对象,这意味着不需要在getter 之后写.value,但是,就像setup 中的props 一样,我们不能对其进行解构:
export default defineComponent({
setup() {
const store = useStore()
// ❌ 这不起作用,因为它会破坏响应式
// 这和从 props 解构是一样的
const { name, doubleCount } = store
name // "eduardo"
doubleCount // 2
return {
// 一直会是 "eduardo"
name,
// 一直会是 2
doubleCount,
// 这将是响应式的
doubleValue: computed(() => store.doubleCount),
}
},
})
为了从 Store 中提取属性同时保持其响应式,您需要使用storeToRefs()。 它将为任何响应式属性创建 refs。 当您仅使用 store 中的状态但不调用任何操作时,这很有用:
import { storeToRefs } from 'pinia'
export default defineComponent({
setup() {
const store = useStore()
// `name` 和 `doubleCount` 是响应式引用
// 这也会为插件添加的属性创建引用
// 但跳过任何 action 或 非响应式(不是 ref/reactive)的属性
const { name, doubleCount } = storeToRefs(store)
return {
name,
doubleCount
}
},
})
订阅机制
当监听的时候进行打印,与常规的 watch() 相比,使用 $subscribe() 的优点是 subscriptions 只会在 patches 之后触发一次(摘自官方文档)。
<!--App.vue-->
<template>
<div>{{state.count}}</div>
<Son></Son>
</template>
<script setup>
import {useCountStore} from './stores/index'
import Son from './components/Son.vue';
const state=useCountStore()
state.$subscribe((mutation, state) => {
console.log(Object.prototype.toString(mutation));
console.log(mutation);
</script>
<!--Son.vue-->
<template>
<div>this is son component</div>
<div>son:{{counter.count}}</div>
<button @click="addCount">+</button>
</template>
<script setup>
import {useCountStore} from '../stores/index'
name:"Son"
const counter=useCountStore()
function addCount(){
counter.count++
}
</script>
订阅函数中传入了2个函数,将他们打印一下,第二个就是我们熟悉的state,而第一个就是mutation,将他打印如图所示,可以知道他的基本结构。
这里知乎还有一个问题值得一看:为什么Pinia不需要mutation了,是怎么实现的?
Getters(计算值)
指定getter返回的值类型
export const useStore = defineStore('main', {
state: () => ({
counter: 0,
}),
getters: {
// 自动将返回类型推断为数字
doubleCount(state) {
return state.counter * 2
},
// 返回类型必须明确设置
doublePlusOne(): number {
return this.counter * 2 + 1
},
},
})
访问getters中进行缓存
如下面规划了数组并且进行操作,最终返回数组操作的结果
访问其他的store
在getters定义相关对象并进行访问