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')
相关推荐
zwjapple4 小时前
docker-compose一键部署全栈项目。springboot后端,react前端
前端·spring boot·docker
像风一样自由20206 小时前
HTML与JavaScript:构建动态交互式Web页面的基石
前端·javascript·html
aiprtem7 小时前
基于Flutter的web登录设计
前端·flutter
浪裡遊7 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
why技术7 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
GISer_Jing7 小时前
0704-0706上海,又聚上了
前端·新浪微博
止观止7 小时前
深入探索 pnpm:高效磁盘利用与灵活的包管理解决方案
前端·pnpm·前端工程化·包管理器
whale fall7 小时前
npm install安装的node_modules是什么
前端·npm·node.js
烛阴7 小时前
简单入门Python装饰器
前端·python
袁煦丞8 小时前
数据库设计神器DrawDB:cpolar内网穿透实验室第595个成功挑战
前端·程序员·远程工作