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')
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
}
}
})
<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)
}
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>
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的变化
countStore.$subscribe((mutations, state) => {
console.log(mutations, state);
})
// 调用$reset方法重置数据
const reset = () => {
countStore.$reset()
}
<button @click="reset">重置</button>
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;
}
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可以是对象或函数
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;
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;
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;
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;
}