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')
相关推荐
持久的棒棒君2 小时前
npm安装electron下载太慢,导致报错
前端·electron·npm
crary,记忆3 小时前
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
前端·webpack·angular·angular.js
漂流瓶jz4 小时前
让数据"流动"起来!Node.js实现流式渲染/流式传输与背后的HTTP原理
前端·javascript·node.js
SamHou04 小时前
手把手 CSS 盒子模型——从零开始的奶奶级 Web 开发教程2
前端·css·web
我不吃饼干4 小时前
从 Vue3 源码中了解你所不知道的 never
前端·typescript
开航母的李大5 小时前
【中间件】Web服务、消息队列、缓存与微服务治理:Nginx、Kafka、Redis、Nacos 详解
前端·redis·nginx·缓存·微服务·kafka
Bruk.Liu5 小时前
《Minio 分片上传实现(基于Spring Boot)》
前端·spring boot·minio
鱼樱前端5 小时前
Vue3+d3-cloud+d3-scale+d3-scale-chromatic实现词云组件
前端·javascript·vue.js
zhangxingchao5 小时前
Flutter入门:Flutter开发必备Dart基础
前端
佚名猫6 小时前
vue3+vite+pnpm项目 使用monaco-editor常见问题
前端·vue3·vite·monacoeditor