setup版本pinia源码分析和实现

vue插件使用方法介绍

vue 插件一般都是

const app = createApp(App)

app.use(MyPlugin, { /* 插件配置选项 */ })

vue会自动调用MyPlugin中的install方法

pinia

pinia也是如此,接下来我们分析一下pinia源码(我会省略相关部分代码,只讲核心代码,比如插件的设计,如果想要了解的同学们,可以自己去看pinia源码),这篇我只讲 setuppiniapinia内部实现兼容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-demivue-demi就是一个vuecomposition的库,他兼容了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就这些东西。

  1. acitvePinia激活当前pinia
  2. this.$pinia可以拿到pinia对象(不重要setup不需要this)
  3. provide inject挂载,可以直接通过inject(piniaSymbol),拿到pinia对象
  4. 创建一个全局的周期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
}

这就是一个最简版本的 仅支持setuppinia,就实现了。

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')
相关推荐
JINGWHALE14 分钟前
设计模式 行为型 模板方法模式(Template Method Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·模板方法模式
&活在当下&33 分钟前
Vue3 给 reactive 响应式对象赋值
前端·vue.js
坐公交也用券1 小时前
VUE3配置后端地址,实现前后端分离及开发、正式环境分离
前端·javascript·vue.js
独孤求败Ace1 小时前
第31天:Web开发-PHP应用&TP框架&MVC模型&路由访问&模版渲染&安全写法&版本漏洞
前端·php·mvc
星星不闪包退换2 小时前
css面试常考布局(圣杯布局、双飞翼布局、三栏布局、两栏布局、三角形)
前端·css
疯狂的沙粒2 小时前
HTML和CSS相关的问题,如何避免 CSS 样式冲突?
前端·css·html
家电修理师3 小时前
HBuilderX打包ios保姆式教程
前端·ios
草木红3 小时前
六、Angular 发送请求/ HttpClient 模块
服务器·前端·javascript·angular.js
kkkkatoq3 小时前
EasyExcel的应用
java·前端·servlet
阿雄不会写代码3 小时前
使用java springboot 使用 Redis 作为限流工具
前端·bootstrap·html