pinia源码解析

为什么要看源码

我理解有以下6点:

  1. 学习编码技巧
  2. 学习设计思想
  3. 提升阅读代码速度
  4. 工作中得心应手,减少心智负担,提升效率
  5. 对技术的使用边界更清晰,在项目前评估技术方案有理有据
  6. 团队成员在使用过程中出现问题,帮助队友,更重要的提升自己,升职加薪(专业奋斗共赢,企业文化传销骨干)

上面除了搞钱都是废话,既然吃这碗饭了,能多赚点就多赚点不丢人

准备工作

了解vue3,js,ts

pinia是什么

看官网

pinia使用方法

看官网

开始

vue3使用pinia

js 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

createPinia()做了什么

packages/src/createPinia.ts

js 复制代码
export function createPinia() {
  const scope = effectScope(true)
  const state = scope.run(() => ref({}))
  let _p = []
  let toBeInstalled = []
  const pinia = markRaw({
    install(app){ ... },
    use(plugin) { ... },
    _p,
    _a: null,
    _e: scope,
    _s: new Map(),
    state,
  })
}

以上有2个方法需要提前了解effectScope,markRaw

effectScope

effectScope官网文档

effectScope的作用

创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理

ts 复制代码
effectScope(detached: boolean)

detached: 是否创建一个分离的效果

effectScope会返回一个对象 对象有两个属性方法runstop

html 复制代码
<script setup lang="ts">
import { effectScope, ref, computed, watch } from 'vue'
const scoped = effectScope(true)
const aaa = ref(0)
scoped.run(() => {
  console.log('run')
  const bbb = computed(() => aaa.value * 2)
  watch(bbb, () => {
    console.log('bbb变了:', bbb.value)
  })
})
function aaaClick() {
  console.log('aaaClick')
  aaa.value += 1
}
function stop() {
  console.log('stop')
  scoped.stop()
}
</script>

<template>
  <button @click="aaaClick">给aaa+1</button>
  <button @click="stop">停止</button>
</template>

我们把示例代码启动一下,可以看到界面并且打印了run说明scoped.run(fn)会立即执行fn这个回掉函数

当我点击2次给aaa+1的按钮,可以看到控制台打印了bbb变了: 2bbb变了: 4

当我再去点击停止,然后连点3次发现bbb变了不会去打印了

这样我们就了解了effectScope

回到源码我们就知道state是个ref({}),其实就是创建一个局部的响应对象

markRaw

markRaw官方文档

markRaw的作用

将一个对象标记为不可被转为代理。返回该对象本身

html 复制代码
<script setup lang="ts">
import { markRaw, reactive, isReactive } from 'vue'
const aaa = {
  a: 1,
  b: 2
}

const bbb = markRaw(aaa)
const ccc = reactive(bbb)
console.log(ccc)

const ddd = reactive({
  a: 1,
  b: 2
})
console.log(ddd)
console.log(isReactive(ddd))
console.log(isReactive(ccc))
</script>

可以明显看到打印ccc是没有proxy代理的,ddd有proxy 这样我们就了解了markRaw

我们再回到createPinia,就理解createPinia其实就是生成pinia对象并返回

app.use(pinia)

其实就是调用pinia.install(app)

js 复制代码
install(app) {
  setActivePinia(pinia)
  pinia._a = app
  app.provide(piniaSymbol, pinia)
  app.config.globalProperties.$pinia = pinia
  toBeInstalled.forEach((plugin) => _p.push(plugin))
  toBeInstalled = []
}

piniaSymbol其实就是Symbol(),app.provide(piniaSymbol, pinia)给整个应用层面提供pinia依赖,这样我们在全局就可以拿到pinia对象

app.config.globalProperties.$pinia = pinia,在组件实例添加$pinia数学即pinia对象

toBeInstalled循环把plugin放到_p数组,然后清空toBeInstalled

defineStore做了什么

packages/src/store.ts

项目中使用示例

js 复制代码
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)
  function increment() {
    count.value++
  }

  return { count, doubleCount, increment }
})
html 复制代码
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const { count, increment } = useCounterStore()
</script>

<template>
  <div>{{ count }}</div>
  <button @click="increment">点击</button>
</template>
js 复制代码
export function defineStore(idOrOptions, setup, setupOptions) {
  let id = idOrOptions
  let options = setupOptions
  function useStore(pinia){...}
  useStore.$id = id
  return useStore
}

defineStore其实就是返回useStore函数,useCounterStore == useStore

useStore

js 复制代码
function useStore(pinia, hot) {
  createSetupStore(id, setup, options, pinia)
  // pinia._s就是map对象
  const store = pinia._s.get(id)
  return store
}

createSetupStore

js 复制代码
function createSetupStore($id, setup, options, pinia) {
  let scope
  const initialState = pinia.state.value[$id]
  if(!initialState) {
     pinia.state.value[$id] = {}
  }
  function $patch(partialStateOrMutator){...}
  function $dispose() {...}
  function wrapAction(name, action) {...}
  const partialStore = {
    _p: pinia,
    $id,
    $patch,
    $dispose
  }
  const store = reactive(partialStore)
  pinia._s.set($id, store)
  // setupStore就是{ count, doubleCount, increment }
  const setupStore = pinia._e.run(() => {
    scope = effectScope()
    return (() => scope.run(setup))()
  })
  for (const key in setupStore) {
    const prop = setupStore[key]
    if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
      pinia.state.value[$id][key] = prop
    } else if (typeof prop === 'function') {
      const actionValue = wrapAction(key, prop)
      // 覆盖increment, actionValue其实就是() => increment.apply(null, ...args)
      // wrapAction把原来increment做一层前置处理
      setupStore[key] = actionValue
    }
  }
  // 合并store, setupStore对象
  assign(store, setupStore)
  // toRaw返回代理对象的原始对象,reactive({ a: 1, b: 2}), toRaw一下返回 {a:1, b:2}
  assign(toRaw(store), setupStore)
  return store
}

其实createSetupStore就是生成store响应对象,里面有一些方法和{ count, doubleCount, increment }, pinia._s.set($id, store)放入到map中

回到useStore返回就是这个store reactive对象,所以执行useCounterStore()返回的就是store reactive对象

加餐

分析了defineStore的源码,大家有没有发现文章使用useCounterStore()的问题

html 复制代码
<script setup lang="ts">
import { useCounterStore } from '@/stores/counter'
const { count, increment } = useCounterStore()
</script>

<template>
  <div>{{ count }}</div>
  <button @click="increment">点击</button>
</template>

就是无论点击执行increment多少次,count在页面中始终没变,我们分析了源码,这个问题就很好解释了, 看下面的代码

html 复制代码
<script setup lang="ts">
import { ref, reactive, watch } from 'vue'
const aaa = ref(0)
const bbb = reactive({
  ccc: aaa
})
const { ccc } = bbb
watch(aaa, (newVal) => {
  console.log('aaa:', newVal)
})
function onClick() {
  aaa.value += 1
  console.log(aaa.value)
}
</script>

<template>
  <div>
    {{ aaa }}
  </div>
  <div>
    {{ ccc }}
  </div>
  <button @click="onClick">点击</button>
</template>

我点击按钮3次,看输出的结果

ccc一直没有变还是0, 问题就在于useCounterStore()返回的是reactive对象,这个问题官方也有解释,reactive对象对解构不友好官方解释

总结

我们学习了createpinia是做pinia插件的准备工作返回一个pinia对象,同时学习了effectScope是给响应式副作用(即计算属性和侦听器)打包成一个作用域一起处理,还学习了markRaw让对象返回一个新的对象并且不能被reactive

并且学习了defineStore本质是返回一个reactive对象,对象里面有一些源码内置的方法和属性同时合并了我们自己传入的回掉函数返回的属性和方法

相关推荐
cs_dn_Jie1 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic2 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿2 小时前
webWorker基本用法
前端·javascript·vue.js
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
清灵xmf3 小时前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
小白学大数据3 小时前
JavaScript重定向对网络爬虫的影响及处理
开发语言·javascript·数据库·爬虫
qq_390161773 小时前
防抖函数--应用场景及示例
前端·javascript
334554324 小时前
element动态表头合并表格
开发语言·javascript·ecmascript
John.liu_Test4 小时前
js下载excel示例demo
前端·javascript·excel
PleaSure乐事4 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro