草庐IT

pinia核心笔记

copyLeft 2023-03-28 原文

pinia 核心源码

记录pinia核心源码阅读笔记,这里跳过hmr(热更新), mapHelpers(class 工具)等工具源码。
剔除的部分vue2.0兼容代码。
当前pinia版本2.0.13

执行流程概述

  1. 创建pinia实例,挂载到vue
  2. 定义state
  3. 创建组件
  4. 调用useState
  5. 生成并缓存pinia
  6. 注销组件
  7. 注销监听
pinia.png

rootStore.js

这里主要提供 activePinia(当前可用pinia实例)缓存对象。
并提供两个操作方法,

  1. setActivePinia 更新 activePinia
export const setActivePinia = (pinia: Pinia | undefined) =>
  (activePinia = pinia)
  1. getActivePinia 获取 activePinia
export const getActivePinia = () =>
  // 这里优先返回全局注册的pinia实例
  (getCurrentInstance() && inject(piniaSymbol)) || activePinia

subscriptions.ts

响应事件相关, 提供两个方法

  1. addSubscription
// 向当前state事件队列中注册事件回调
export function addSubscription<T extends _Method>(
  subscriptions: T[],
  callback: T,
  detached?: boolean,
  onCleanup: () => void = noop
) {
  subscriptions.push(callback)

  const removeSubscription = () => {
    const idx = subscriptions.indexOf(callback)
    if (idx > -1) {
      subscriptions.splice(idx, 1)
      onCleanup()
    }
  }

  // 默认组件注销时,清理事件回调
  if (!detached && getCurrentInstance()) {
    onUnmounted(removeSubscription)
  }

  return removeSubscription
}

  1. triggerSubscriptions
// 执行事件队列
export function triggerSubscriptions<T extends _Method>(
  subscriptions: T[],
  ...args: Parameters<T>
) {
  subscriptions.slice().forEach((callback) => {
    callback(...args)
  })
}

createPinia.ts

创建pinia实例

export function createPinia(): Pinia {
  
  // 创建响应式空间,空值pinia相关的响应对象的有效性
  const scope = effectScope(true)
  
  // state缓存空间, 生成的store将缓存到该队列中
  // 当使用useState是,将通过注册的id,从stateTrue
  // 中查询对应的store,保证不同组件使用相同的store
  const state = scope.run<Ref<Record<string, StateTree>>>(() =>
    ref<Record<string, StateTree>>({})
  )!

  // 插件队列
  let _p: Pinia['_p'] = []
  let toBeInstalled: PiniaPlugin[] = []

  // 创建pinia实例
  // markRaw 保证pinia不会被代理
  const pinia: Pinia = markRaw({

    // 将pinia实例注册到Vue实例中
    install(app: App) {

      // 激活当前pinia实例, 
      setActivePinia(pinia)
      if (!isVue2) {

        // 设置vue实例
        pinia._a = app
        // 通过依赖注入设置全局默认pinia实例
        // 后面useState会用到
        app.provide(piniaSymbol, pinia)
        // 挂载全局pinia实例
        app.config.globalProperties.$pinia = pinia

        if (__DEV__ && IS_CLIENT) {
          registerPiniaDevtools(app, pinia)
        }
        // 添加插件
        toBeInstalled.forEach((plugin) => _p.push(plugin))
        toBeInstalled = []
      }
      
    },

    // 注册插件
    use(plugin) {
      if (!this._a && !isVue2) {
        toBeInstalled.push(plugin)
      } else {
        _p.push(plugin)
      }
      return this
    },

    // 插件集合
    _p,
    // 应用实例
    _a: null,
    // 响应空间
    _e: scope,
    // store 队列
    _s: new Map<string, StoreGeneric>(),
    // state配置队列, 用于重置state
    state,
  })

  return pinia
}

这里主要创建pinia实例,如果pinia实例被注册要vue应用实例时,将执行一些初始值设置,依赖注册pinia实例,以供useState使用

store.ts

pinia状态, 主要包括三个核心

  1. defineStore 定义状态
  2. createOptionsStore 对象型状态生成函数 defineStore(id, {state, getter, action})
  3. createSetupStore 函数型状态生成函数 defineStore(id, () => { setup(){} })

defineStore 定义store

defineStore 只做了两件事

  1. 参数处理
  2. 构建useState函数
    这里主要看useState做了什么
// 通过配置类型判断配置类型 
const isSetupStore = typeof setup === 'function'
...

// useState 可接收一个pinia实例作为参数
// 如果设置参数pinia,将通过依赖注入获取全局默认pinia实例
pinia =
   (__TEST__ && activePinia && activePinia._testing ? null : pinia) ||
   (currentInstance && inject(piniaSymbol))

// 激活当前pinia实例
if (pinia) setActivePinia(pinia)

// 通过 id查询对应的store是否已经创建
if (!pinia._s.has(id)) {
      
      // 如果未在当前pinia上查到对应store, 将根据参数类型创建store
      if (isSetupStore) {
        // 函数型
        createSetupStore(id, setup, options, pinia)
      } else {
        // 对象型
        createOptionsStore(id, options as any, pinia)
      }
     ...
    }

// 如果store存在返回该实例
const store: StoreGeneric = pinia._s.get(id)!
...
return store as any


// 其他
// 在创建useStore函数后
// 将当前id挂载useStore.$id属性上
useStore.$id = id

createOptionsStore 对象型store生成

这个函数其实是createSetupStore的包装函数, 将对象型的定义转为函数型
再交由createOptionsStore生成store

store生成

// 这里会先将options转为setup函数
// 通过createSetupStore生成store实例
store = createSetupStore(id, setup, options, pinia, hot)

// 绑定重置函数
store.$reset = function $reset() {
 // state 这里是闭包
 const newState = state ? state() : {}
 // this指向store
 // this.$patch 是state更新函数, 
 this.$patch(($state) => {
   // 将原state与现有state合并,将state部分属性值重置
   assign($state, newState)
 })
}

setup函数

这里看setup函数做了什么

function setup() {
  ...

// 初始将state缓存到当前pinia.state中
pinia.state.value[id] = state ? state() : {}

// 将state转未ref
const localState = toRefs(pinia.state.value[id])

// 返回响应对象
  return assign(
   localState, // state => Refs(state)
   actions, //   actions => actions
    // 遍历getters, 将属性包裹一层computed
   Object.keys(getters || {}).reduce((computedGetters, name) => {
     // markRow 防止对象被重复代理
     computedGetters[name] = markRaw(
       computed(() => {

         // pinia 处于闭包
         setActivePinia(pinia)
         // it was created just before
         const store = pinia._s.get(id)!

         // 将执行函数绑定在store上下文中,支持 {getters: { fn(){ this.count++ } }} 模式
         // 所以当使用箭头函数时不能使用this获取state
         // 函数接收state作为参数, 支持{gtters: { f(state){state.count++ } }}
         // 返回getter执行结果
         return getters![name].call(store, store)
       })
     )
     return computedGetters}
    {}
  )
}

所以setup主要作用是 1.将getter包裹computed, 2.返回新的store定义,通过getter的包装过程,知道了为什么箭头函数不能使用this模式,主要应为箭头函数的this原定义上下文绑定,后期无法通过call函数绑定到state上。

createSetupStore 函数型store生成

生成并挂载store实例

公共变量

let isListening: boolean // 监听函数执行时机标识
let isSyncListening: boolean // 监听函数执行时机标识
// state 更新响应队列,缓存¥subscribe挂载的任务
let subscriptions: SubscriptionCallback<S>[] = markRaw([])
// actions 响应事件队列, 缓存$onAction挂载的任务
let actionSubscriptions: StoreOnActionListener<Id, S, G, A>[] = markRaw([])
// debugger 事件队列
let debuggerEvents: DebuggerEvent[] | DebuggerEvent
// 初始缓存state
const initialState = pinia.state.value[$id] as UnwrapRef<S> | undefined

store 实例

因为createSetupStore的主要功能就是生成store实例,所以这里先看生成的store
主要步骤


// 如果state不存在,设置默认值
 if (!buildState && !initialState && (!__DEV__ || !hot)) {
   pinia.state.value[$id] = {}
 }

// store基础方法属性
// 这里主要定义store实力的操作API
const partialStore = {
  _p: pinia,
 // action 响应事件注册函数
 $onAction: addSubscription.bind(null, actionSubscriptions),
 // state 更新函数
 $patch,
 // 重置store
 $reset,
// 注册响应修改监听
 $subscribe(callback, options = {}) {...},
 // 注销store
  $dispose,
}

// 转为响应对象
const store: Store<Id, S, G, A> = reactive(
  assign({}, partialStore)
)

// 缓存store, useState通过当前激活的pinia获取到store
pinia._s.set($id, store)

// 合并store
// setupStore为setup()执行处理后配置对象
// 主要是对action的包装以及部分属性的合并
assign(store, setupStore)
// 这里为了 storeToRefs, 将响应属性合并到store原对象上
// storeToRefs 将先取得toRaw(store)再说Refs处理
assign(toRaw(store), setupStore)

// 绑定$state属性
Object.defineProperty(store, '$state', {
 get: () => pinia.state.value[$id],
 set: (state) => {
   $patch(($state) => {
     assign($state, state)
   })
 },
})

这里剔除的具体的方法定义,和周期函数的调用,主要看store的基础生成。

$patch state更新


 function $patch(
    partialStateOrMutator:
      | _DeepPartial<UnwrapRef<S>>
      | ((state: UnwrapRef<S>) => void)
  ): void {
    
    let subscriptionMutation: SubscriptionCallbackMutation<S>
    
    // 阻止$subscribe监听事件执行
    // 防止重复触发
    // 保证$subscribe在完整合并后再执行
    isListening = isSyncListening = false
    
    if (__DEV__) {
      debuggerEvents = []
    }

    // 如果状态修改器为函数,执行并生成修改类型
    if (typeof partialStateOrMutator === 'function') {
      // 例如 $reset()
      partialStateOrMutator(pinia.state.value[$id] as UnwrapRef<S>)

      // 函数更新类型
      subscriptionMutation = {
        type: MutationType.patchFunction,
        storeId: $id,
        events: debuggerEvents as DebuggerEvent[],
      }
    } else {
      // 如果状态修改器为对象, 合并到新state中
      // mergeReactiveObjects将递归合并对象内的属性
      mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator)
      
      // 对象更新类型
      subscriptionMutation = {
        type: MutationType.patchObject,
        payload: partialStateOrMutator,
        storeId: $id,
        events: debuggerEvents as DebuggerEvent[],
      }
    }

    // 开启监听锁
    nextTick().then(() => {
      isListening = true
    })
    isSyncListening = true

    // 应为之前关闭了watch监听, 所以这里需要手动执行一次监听队列
    triggerSubscriptions(
      subscriptions,
      subscriptionMutation,
      pinia.state.value[$id] as UnwrapRef<S>
    )
  }
  

subscribe绑定的事件将在state更新后被执行一次

$subscribe 更新监听

 $subscribe(callback, options = {}) {

   // 向任务队列中添加任务, 并返回移除函数
   const removeSubscription = addSubscription(
     subscriptions,
     callback,
     options.detached,
     // 这里有个问题 stopWatcher 先于定义,const应该存在假死区 
     () => stopWatcher()
   )
   // 挂载更新监听 
   const stopWatcher = scope.run(() =>
     watch(
       () => pinia.state.value[$id] as UnwrapRef<S>,
       (state) => {
         // 更新锁, patch时禁用更新监听
         if (options.flush === 'sync' ? isSyncListening : isListening) {
           callback(
             {
               storeId: $id,
               type: MutationType.direct,
               events: debuggerEvents as DebuggerEvent,
             },
             state
           )
         }
       },
       assign({}, $subscribeOptions, options)
     )
   )!

   return removeSubscription
 }

wrapAction

action 包装函数,主要为了提供 $onAction 监听钩子,
该函数在setupStore生成时被调用

 function wrapAction(name: string, action: _Method) {
    return function (this: any) {
      setActivePinia(pinia)
      const args = Array.from(arguments)

      // action执行后回调队列
      const afterCallbackList: Array<(resolvedReturn: any) => any> = []
      // 错误回调队列
      const onErrorCallbackList: Array<(error: unknown) => unknown> = []
      
      // action执行后回调添加函数
      function after(callback: _ArrayType<typeof afterCallbackList>) {
        afterCallbackList.push(callback)
      }
      // 错误回调添加函数
      function onError(callback: _ArrayType<typeof onErrorCallbackList>) {
        onErrorCallbackList.push(callback)
      }
      
      // 执行action任务队列
      triggerSubscriptions(actionSubscriptions, {
        args,
        name,
        store,
        after,
        onError,
      })

      let ret: any

      try {
        ret = action.apply(this && this.$id === $id ? this : store, args)
      } catch (error) {
        triggerSubscriptions(onErrorCallbackList, error)
        throw error
      }
      
      // 异步函数处理
      if (ret instanceof Promise) {
        return ret
          .then((value) => {
            triggerSubscriptions(afterCallbackList, value)
            return value
          })
          .catch((error) => {
            triggerSubscriptions(onErrorCallbackList, error)
            return Promise.reject(error)
          })
      }

      triggerSubscriptions(afterCallbackList, ret)
      return ret
    }
  }

执行流程 $onAction监听队列 -> action -> after任务队列 or error任务队列
应为onAction本身可以看作 beforeCallbackList, action的前置监听队列

其他钩子

  1. plugins
// 生成store后将执行插件函数
pinia._p.forEach((extender) => {...}
  1. hydrate
// 执行plugins后执行合并函数
(options as DefineStoreOptions<Id, S, G, A>).hydrate!(
  store.$state,
  initialState
)

总结

pinia核心代码并不多,主要功能放在了store生成,钩子包装。
值得注意的是:

  1. pinia实例的调用
  2. scope 空值响应作用空间
  3. 钩子的调度
  4. 兼容支持

疑问

  1. $subscribe 监听中 stopWatcher 变量先于定义
 const removeSubscription = addSubscription(
   ....
  () => stopWatcher()
)
const stopWatcher = scope.run(() =>{...})

  1. 部分属性遍历上是否可以用其他的方法

// 使用了 for in 遍历,将获取到原型上方法
for (const key in patchToApply) {
 if (!patchToApply.hasOwnProperty(key)) continue
 const subPatch = patchToApply[key]
 const targetValue = target[key]

有关pinia核心笔记的更多相关文章

  1. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  2. Unity Shader 学习笔记(5)Shader变体、Shader属性定义技巧、自定义材质面板 - 2

    写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c

  3. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  4. ruby - 如何让 ruby​​-prof 忽略 Ruby 核心/标准库/gem 方法? - 2

    我是Ruby分析的新手,看起来像ruby-prof是一个受欢迎的选择。我刚刚安装了gem并调用了我的程序:ruby-prof./my-prog.rb但是,输出非常冗长,因为包含所有Ruby核心和标准库方法以及其他gem的分析数据。例如,前三行是:8.790.0110.0100.0000.0013343*String#%7.280.0780.0090.0000.0692068*Array#each4.930.0380.0060.0000.0321098*Array#map这对我来说不是什么有用的信息,因为我已经知道我的程序经常处理字符串和数组,并且大概已经对这些类进行了优化。我只关心我代

  5. [面试直通版]操作系统核心之进程、线程与协程(下) - 2

    点击->操作系统复习的文章集目录操作系统线程线程是什么进程与线程的关系用户态/内核态操作系统资源管理内核态用户态内核态/用户态切换程序运行类型分析计算密集型IO密集型结合进程,线程来理解程序运行类型分析协程基础上下文切换协程协程为什么叫协作式线程?协程的优缺点操作系统线程典型问题:简述进程和线程的区别以下内容带您一步步了解线程是什么比进程更小的独立运行的基本单位-线程(Threads)线程的提出主要是为了提高系统内程序并发执行的程度,从而进一步提升系统的吞吐量,充分发挥多核CPU的优越性而设计的引入进程是为了操作系统更加方便地管理程序,使得多个程序能并发管理和执行而线程则是为了减少程序在并发执

  6. 计算机网络笔记:TCP三次握手和四次挥手过程 - 2

    TCP是面向连接的协议,连接的建立和释放是每一次面向连接的通信中必不可少的过程。TCP连接的管理就是使连接的建立和释放都能正常地进行。三次握手TCP连接的建立—三次握手建立TCP连接①若主机A中运行了一个客户进程,当它需要主机B的服务时,就发起TCP连接请求,并在所发送的分段中用SYN=1表示连接请求,并产生一个随机发送序号x,如果连接成功,A将以x作为其发送序号的初始值:seq=x。主机B收到A的连接请求报文,就完成了第一次握手。客户端发送SYN=1表示连接请求客户端发送一个随机发送序号x,如果连接成功,A将以x作为其发送序号的初始值:seq=x②主机B如果同意建立连接,则向主机A发送确认报

  7. 设计一个亿级高并发系统架构 - 12306火车票核心场景DDD领域建模 - 2

    “架设一个亿级高并发系统,是多数程序员、架构师的工作目标。许多的技术从业人员甚至有时会降薪去寻找这样的机会。但并不是所有人都有机会主导,甚至参与这样一个系统。今天我们用12306火车票购票这样一个业务场景来做DDD领域建模。”开篇要实现软件设计、软件开发在一个统一的思想、统一的节奏下进行,就应该有一个轻量级的框架对开发过程与代码编写做一定的约束。虽然DDD是一个软件开发的方法,而不是具体的技术或框架,但拥有一个轻量级的框架仍然是必要的,为了开发一个支持DDD的框架,首先需要理解DDD的基本概念和核心的组件。一.什么是领域驱动设计(DDD)首先要知道DDD是一种开发理念,核心是维护一个反应领域概

  8. 华为数通笔记VXLAN&BGP EVPN - 2

    VXLAN简介定义RFC定义了VLAN扩展方案VXLAN(VirtualeXtensibleLocalAreaNetwork,虚拟扩展局域网)。VXLAN采用MACinUDP(UserDatagramProtocol)封装方式,是NVO3(NetworkVirtualizationoverLayer3)中的一种网络虚拟化技术。目的随着网络技术的发展,云计算凭借其在系统利用率高、人力/管理成本低、灵活性/可扩展性强等方面表现出的优势,已经成为目前企业IT建设的新趋势。而服务器虚拟化作为云计算的核心技术之一,得到了越来越多的应用。服务器虚拟化技术的广泛部署,极大地增加了数据中心的计算密度;同时,为

  9. [蓝桥杯单片机]学习笔记——串口通信的基本原理与应用 - 2

    目录一、原理部分1、什么是串行通信(1)并行通信与串行通信(2)串行通信的制式(3)串行通信的主要方式  2、配置串口(1)SCON和PCON:串行口1的控制寄存器(2)SBUF:串行口数据缓冲寄存器 (3)AUXR:辅助寄存器​编辑(4)ES、PS:与串行口1中断相关的寄存器(5)波特率设置  3、串口框架编写二、程序案例一、原理部分1、什么是串行通信(1)并行通信与串行通信微控制器与外部设备的数据通信,根据连线结构和传送方式的不同,可以分为两种:并行通信和串行通信。并行通信:数据的各位同时发送与接收,每个数据位使用一条导线,这种方式传输快,但是需要多条导线进行信号传输。串行通信:数据一位一

  10. 【微服务笔记23】使用Spring Cloud微服务组件从0到1搭建一个微服务工程 - 2

    这篇文章,主要介绍如何使用SpringCloud微服务组件从0到1搭建一个微服务工程。目录一、从0到1搭建微服务工程1.1、基础环境说明(1)使用组件(2)微服务依赖1.2、搭建注册中心(1)引入依赖(2)配置文件(3)启动类1.3、搭建配置中心(1)引入依赖(2)配置文件(3)启动类1.4、搭建API网关(1)引入依赖(2)配置文件(3)启动类1.5、搭建服务提供者(1)引入依赖(2)配置文件(3)启动类1.6、搭建服务消费者(1)引入依赖(2)配置文件(3)启动类1.7、运行测试一、从0到1搭建微服务工程1.1、基础环境说明(1)使用组件这里主要是使用的SpringCloudNetflix

随机推荐