vue插件使用方法介绍
vue
插件一般都是
const app = createApp(App)
app.use(MyPlugin, { /* 插件配置选项 */ })
vue
会自动调用MyPlugin
中的install
方法
pinia
pinia
也是如此,接下来我们分析一下pinia
源码(我会省略相关部分代码,只讲核心代码,比如插件的设计,如果想要了解的同学们,可以自己去看pinia
源码),这篇我只讲 setup
的pinia
,pinia
内部实现兼容vue2 vue3 vuex
,下章我会直接过源码,从浅入深讲解。
pinia -- createPinia
createPinia 使用方式
javascript
import { createPinia } from 'pinia'
app.use(createPinia())
createPinia源码
javascript
import { ref, App, markRaw, effectScope, isVue2, Ref } from 'vue-demi'
export function createPinia() {
const scope = effectScope(true)
//state 返回值就是 state = ref({})
const state = scope.run(() => ref({}))
const pinia = ... //先省略这里的代码后面讲解
return pinia
}
首先我们分析代码第一行是vue-demi
,vue-demi
就是一个vue
的composition
的库,他兼容了vue2.0和3.0
,让2.0和3.0都支持composition
,vue2.7已经支持composition
,本质可以理解成import{ref,...} from 'vue'
代码刚开始是一个effectScope
,不了解的可以去了解一下,我在这里说一下这个effectScope
javascript
import {ref,watch} from 'vue'
let effect = effectScope();
let a = ref(null);
let b = ref(null);
effect.run(()=>{
watch(a, (val)=>{
console.log(val)
})
watch(b, (val)=>{
console.log(val)
})
})
effect.stop();
// effectscope是一个函数,effect.run(callback)传递一个函数 .run方法会执行这个回掉函数
//.stop() 会取消监听 比如这个列子,.stop之后会两个watch就会都失效
// effectscope的目的就是可以取消多个监听
effectscope
的作用就是可以批量取消监听。
省略的pinia代码
接下来我们说省略的pinia
,说pinia
前,先说写几个工具函数
javascript
let activePinia = null
export const setActivePinia = (pinia) => (activePinia = pinia)
export const getActivePinia = () => activePinia
export const piniaSymbol = symbol()
回归正题,请看代码
scss
const pinia: Pinia = markRaw({
install(app: App) {
//激活pinia
setActivePinia(pinia)
//拿到createApp对象
pinia._a = app
app.provide(piniaSymbol, pinia)
//挂载全局变量,这样this.$pinia就可以拿到这个pinia对象了
app.config.globalProperties.$pinia = pinia
},
_a: null,
//这个scope就是上面的effectscope
_e: scope,
_s: new Map(),
//state是上面的const state = scope.run(() => ref({}))
state,
})
我们上边说了vue
插件,vue
自动调用这个install
方法。 createPinia
就这些东西。
- acitvePinia激活当前pinia
- this.$pinia可以拿到pinia对象(不重要setup不需要this)
- provide inject挂载,可以直接通过inject(piniaSymbol),拿到pinia对象
- 创建一个全局的周期effectscope
接下来我们看defineStore
,这才是关键
defineStore
先是一个setup
使用案例
javascript
import {ref, computed} from 'vue';
import {defineStore} from 'pinia';
export const useAppStore = defineStore('app', () => {
const data = ref<any>({a: 1});
const a = computed(() => {
return data.value.a;
});
async function setdata() {
data.value = {a: 2};
}
return {
data,
a,
setdata
};
});
接下来看 defineStore
的源码
scss
export function defineStore(
id,
setup,
setupOptions?
){
function useStore(pinia) {
if (pinia) {
setActivePinia(pinia)
} else {
pinia = getActivePinia()
}
//判断current store 有没有被初始化
if (!pinia._s.has(id)) {
// 初始化
createSetupStore(id, setup, setupOptions, pinia)
}
const store: StoreGeneric = pinia._s.get(id)!
return store as any
}
useStore.$id = id
return useStore
}
你会发现这defineStore
没什么东西,那就继续看createSetupStore
scss
function createSetupStore($id, setup, setupOptions, pinia) {
//对应effectScope,只是一开始没有赋值
let scope;
const initialState = pinia.state.value[$id]
// 初始化数据
if(!initialState) {
pinia.state.value[$id] = {}
}
//setup模式不支持reset
const $reset = () => {}
//取消挂载
const $dispose = function () {
scope && scope.stop()
pinia._s.delete($id)
}
const partialStore = {
_p:pinia,
$id,
$reset,
$dispose
}
const store = reactive({...partialStore});
pinia._s.set($id, store)
const setupStore = (()=>{
//pinia._e是最外的effectscope,之所以要用最外层.run是因为,最外层摧毁,大家都摧毁
//本身scope为什么还需要考虑effectScope,因为可能只是自己的一个store摧毁
//所以这里有两层effectcope嵌套,根毁都毁,枝毁根不毁
pinia._e.run(() => (scope = effectScope()).run(setup)!)
})()
for (const key in setupStore) {
const prop = setupStore[key]
// ref(data) or reactive({...})
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
//对象挂载到pinia上面,但是还没有挂载在store上
pinia.state.value[$id][key] = prop
} else if (typeof prop === 'function') {
setupStore[key] = actionValue
}
//把setupStore挂载到store上
Object.assign(store, setupStore)
// storeToRefs()
Object.assign(toRaw(store), setupStore)
}
return store
}
这就是一个最简版本的 仅支持setup
的pinia
,就实现了。
demo
我们再来想一个demo
ini
export const useAppStore = defineStore('app', () => {
const data = ref<any>({a: 1});
const a = computed(() => {
return data.value.a;
});
async function setdata() {
data.value = {a:2};
}
return {
data,
a,
setdata
};
});
xml
<template>
{{appStore.a}}
</template>
<script lang="ts" setup>
import {useAppStore} from '@/pinia/app';
const appStore = useAppStore();
setTimeout(() => {
appStore.setdata()
}, 1000);
</script>
scss
import App from './app.vue'
let pinia = createPinia();
createApp(App).use(pinia).mount('#root')