基本使用
- 在main.js中注册pinia
import { Vue,createApp } from 'vue'
import App from './App'
const app = createApp(App)
// 引入pinia
import { createPinia } from 'pinia'
// 初始化pinia,并注册
const pinia = createPinia()
app.use(pinia).mount('#app')
- /store/counter.js 声明store的配置
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter',{
state(){
return {
count: 10,
price: 100
}
},
getters:{
totalPrice(){
return `¥${this.count * this.price}`;
}
},
actions:{
increment(num){
this.count += num
}
}
})
- 在App.vue中使用store
<script setup>
// 引用counter仓库
import { useCounterStore } from '@/store/counter'
// 初始化仓库
const store = useCounterStore();
</script>
<template>
<button type="button" @click="handleChangeSum">count is: {{ countStore.count }}</button>
<button type="button">price is: {{ countStore.price }}</button>
<h1>总价格:{{ countStore.totalPrice }}</h1>
</template>
一、修改属性的四种方式
/**
* 1. 直接修改
* 因为pinia中的state属性都是响应式的,pinia支持直接修改属性
*/
const dispatchIncrement = ()=>{
store.count+=100;
}
/**
* 2. 使用$patch更改属性
* $patch支持两种修改属性的方法(对象形式或回调函数形式)
*/
const dispatchIncrement = ()=>{
// $patch对象形式
store.$patch({ count: store.count + 100})
}
/**
* 3. 使用$patch更改属性 (回调函数形式)
*/
const dispatchIncrement = ()=>{
// $patch对象形式
store.$patch((state)=>{ state.count+=100 })
}
/**
* 4. 使用actions修改属性
*/
const dispatchIncrement = ()=>{
store.increment(100)
}
二、state属性解构实现响应式
import { useCounterStore } from '@/store/counter'
import { storeToRefs } = 'pinia'
// 初始化仓库
const store = useCounterStore();
// 通过storeToRefs实现解构后依然是响应式状态 (内部通过toRef实现)
const { count,price,totalPrice } = storeToRefs(store)
<template>
<button type="button" @click="handleChangeCount">count is: {{ count }}</button>
<button type="button">price is: {{ price }}</button>
<h1>总价格:{{ totalPrice }}</h1>
</template>
三、actions
counter.ts
export const useCounterStore = defineStore('counter', {
actions: {
getRandomNum(delay: number): Promise<number> {
return new Promise(resolve => {
setTimeout(() => {
resolve(Math.random());
}, delay);
});
},
},
});
App.vue
import { useCounterStore } from '@/store/counter'
const store = useCounterStore()
const getRandomNumClick = async () => {
// 两秒之后获取一个随机数
const number = await store.getRandomNum(2000);
console.log(number);
}
<template>
<button @click="getRandomNumClick">获取随机数</button>
</template>
三、监听store的变化
// 监听store的变化
countStore.$subscribe((mutations, state) => {
console.log(mutations, state);
})
四、重置数据
// 调用$reset方法重置数据
const reset = () => {
countStore.$reset()
}
<button @click="reset">重置</button>
源码分析与实现
一、createPinia
- 该方法返回一个pinia对象,内部提供install方法,方便注册
- _a 用于保存Vue的实例对象
- _m 参数用于保存所有的模块
- _e 最外层的作用域scope
- state 通过作用域创建的ref对象,初始值是一个空对象{}
import { markRaw,EffectScope } from 'vue'
import type { App } from 'vue'
interface Pinia {
install:(app:App)=>void
_e: EffectScope;
_m: Map<any, any>;
_a?:App;
state:Ref<Record<string,any>>
}
export function craetePinia(){
// 创建一个scope用于控制依赖收集
const scope = effectScope(true);
// 初始化一个state 用于保存store所有的状态
const state = scope.run(()=>ref({}))!
// 声明一个pinia仓库(不能被响应式)
const pinia = markRaw<Pinia>({
install(app:App){
// 保存Vue的实例对象
pinia._a = app;
// 将pinia注入组件
app.provide(SymbolPinia,pinia);
// 将pinia挂载到全局
app.config.globalProperties.$pinia = pinia;
}
_e: scope, // pinia依赖收集的作用域
_m: new Map, // 管理仓库的集合
state // 存放所有的状态
})
return pinia;
}
二、defineStore
- store对象
- 每一个store都是一个reactive对象
- 处理state,getters,actives,将三者中的属性与store合并
- 将合并好的store对象存到pinia._m的集合内,key为该仓库id,值为store
- state
- 从模块的配置项中取出state并执行
- 通过toRefs将state中的属性转为响应式
- 将结果合并到store
- getters
- 每一个getter都是一个计算属性的结果,具有缓存特性,getter中的this指向store
- 重新为getter赋值,他的结果是computed的结果,并且在计算属性内通过call调用原始getter函数
- 将结果合并到store
- actions
- 重写action的方法,通过apply调用原始action,改变action函数的this指向
- 将结果合并到store
import {
computed,
effectScope,
getCurrentInstance,
inject,
reactive,
toRefs,
ComputedRef,
UnwrapRef,
isRef,
isReactive,
} from 'vue';
import { SymbolPinia } from './rootStore';
import { Pinia, StoreOptions, StoreOptionsId, StateTree } from './types';
import { isObject } from './utils';
// defineStore第一个参数可以是id 或者是一个配置项
export function defineStore(idorOptions: string, options: StoreOptions): () => void;
export function defineStore(idorOptions: StoreOptionsId): () => void;
export function defineStore(idorOptions: string | StoreOptionsId, optionsSetup?: StoreOptions) {
let id: string;
let options: StoreOptions | StoreOptionsId;
// 用户传入的可能第一个值是字符串的id
if (typeof idorOptions === 'string') {
id = idorOptions;
options = optionsSetup!;
} else if (typeof idorOptions === 'object') { //传入的第一个参数是一个包含id的配置项
id = idorOptions.id;
options = idorOptions;
}
// 创建这个store 并添加到pinia._m中
function useStore() {
// 获取组件的实例
const currentInstance = getCurrentInstance();
// 使用inject获取pinia
const pinia = currentInstance && inject<Pinia>(SymbolPinia);
// 从pinia._m属性中获取仓库
let store = pinia?._m.get(id);
// 第一次获取没有这个仓库 则创建仓库
if (!store) pinia?._m.set(id, (store = createOptionsStore(id, options, pinia)));
return store;
}
return useStore;
}
function createOptionsStore(id: string, options: StoreOptions | StoreOptionsId, pinia: Pinia) {
// 从配置中取出用于创建的state actions getters属性
let { state, actions, getters } = options;
// 每一个仓库都是一个响应式对象
let store = reactive({});
function setup() {
/**
* 处理state
* 将state中的数据添加到pinia.state中
* state中所有的值都应该是响应式的
*/
pinia.state.value[id] = state ? state() : {};
const localState = toRefs(pinia.state.value[id]) as any;
/**
* 处理getters
* 因为每一个getter都是一个具有缓存的计算属性,直接使用computed处理即可
*/
let localGetters = Object.keys(getters || {}).reduce((computedGetters, name) => {
computedGetters[name] = computed(() => {
return getters?.[name].call(store, store);
});
return computedGetters;
}, {} as Record<string, ComputedRef>);
// 返回处理后的结果
return Object.assign(localState, actions, localGetters);
}
// 往最外层的作用域内添加依赖(最外层作用域的scope可以管理所有模块的依赖)
const setupStore = pinia._e.run(() => {
// 每一个store也有自己的作用域effect
const scope = effectScope();
// 使用setup收集参数
return scope.run(() => setup());
});
/**
* 处理actions
* 改变action函数的this执行
*/
for (let key in setupStore) {
let prop = setupStore[key];
// 拦截action并改写action的方法
if (typeof prop === 'function') {
prop = wrapAction(key, prop);
}
}
function wrapAction(key: string, actionValue: Function) {
return function (...args: any[]) {
let res = actionValue.apply(store, args);
return res;
};
}
// 返回一个响应式的store对象
return Object.assign(store, setupStore);
}
三、$patch()
- 合并更新操作,参数可以是对象或函数
- 新值与旧值嵌套对象的情况下,递归拷贝覆盖
// $patch可以是对象或函数
function $patch(stateMutation: (state: UnwrapRef<S>) => void): void;
function $patch(partialState: _DeepPartial<UnwrapRef<S>>): void;
function $patch(
partialStateOrMutator: _DeepPartial<UnwrapRef<S>> | ((state: UnwrapRef<S>) => void)
) {
if (typeof partialStateOrMutator === 'function') {
// 函数直接执行
partialStateOrMutator(pinia._m.get(id));
} else {
// 对象选择拷贝覆盖
mergeReactiveObjects(pinia._m.get(id), partialStateOrMutator);
}
}
function mergeReactiveObjects<T extends StateTree>(target: T, patchToApply: _DeepPartial<T>): T {
// 将数据合并到store中
for (let key in patchToApply) {
// 原型链上的属性不做处理
if (!patchToApply.hasOwnProperty(key)) continue
let subPatch = patchToApply[key]!; // 新的数据
let targetValue = target[key]; // 旧的数据
// 新数据和旧数据仍然是对象的话 需要递归处理 (ref和reactive对象不做处理)
if (
isObject(subPatch) &&
isObject(targetValue) &&
target.hasOwnProperty(key) &&
!isRef(subPatch) &&
!isReactive(subPatch)
) {
// 递归拷贝
target[key] = mergeReactiveObjects(targetValue, subPatch);
} else {
// @ts-expect-error
target[key] = subPatch;
}
}
return target;
}
const partialStore = {
$patch,
};
// 返回合并后的整个store对象
Object.assign(store, partialStore, setupStore);
return store;
四、$reset()
- $reset函数用于重置state为初始状态
- 重新调用state方法,使用$patch更新
Object.assign(store, partialStore, setupStore);
/**
* 重置state中的状态
*/
store.$reset = function () {
// 重新获取state的结果
const newState = state ? state() : {};
// 使用patch将原始结果更新
this.$patch(($state: _DeepPartial<UnwrapRef<S>>) => {
Object.assign($state, newState);
});
};
return store;
五、$subscript()
- $subscript函数用于监听state中属性的变化
- 内部使用watch实现
const partialStore = {
$patch,
// 监听属性的变化
$subscript(callback: Function, options = {}) {
scope.run(() => {
watch(pinia.state.value[id], $state => {
callback({ id, type: 'direct' }, $state);
});
});
},
};
Object.assign(store, partialStore, setupStore);
return store;
六、storeToRefs
- 将store中的属性通过toRef进行转为响应式属性
import { isReactive, isRef, toRaw, toRef } from 'vue';
import { StateTree } from './types';
export function storeToRefs(store: StateTree) {
store = toRaw(store);
const ref = {} as Partial<typeof store>;
for (let key in store) {
if (isRef(store[key]) || isReactive(store[key])) {
ref[key] = toRef(store, key);
}
}
return ref;
}