Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >pinia核心笔记

pinia核心笔记

作者头像
copy_left
发布于 2022-05-13 10:49:06
发布于 2022-05-13 10:49:06
1.1K00
代码可运行
举报
文章被收录于专栏:方球方球
运行总次数:0
代码可运行

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实例)缓存对象。 并提供两个操作方法,

setActivePinia 更新 activePinia

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export const setActivePinia = (pinia: Pinia | undefined) =>
  (activePinia = pinia)

getActivePinia 获取 activePinia

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export const getActivePinia = () =>
  // 这里优先返回全局注册的pinia实例
  (getCurrentInstance() && inject(piniaSymbol)) || activePinia

subscriptions.ts

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

addSubscription

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 向当前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
}

triggerSubscriptions

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 执行事件队列
export function triggerSubscriptions<T extends _Method>(
  subscriptions: T[],
  ...args: Parameters<T>
) {
  subscriptions.slice().forEach((callback) => {
    callback(...args)
  })
}

createPinia.ts

创建pinia实例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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做了什么
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 通过配置类型判断配置类型 
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生成
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 这里会先将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函数做了什么

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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实例

公共变量
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 主要步骤

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 如果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更新
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 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 更新监听
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 $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生成时被调用

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 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的前置监听队列

其他钩子

plugins

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 生成store后将执行插件函数
pinia._p.forEach((extender) => {...}

hydrate

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 执行plugins后执行合并函数
(options as DefineStoreOptions<Id, S, G, A>).hydrate!(
  store.$state,
  initialState
)

总结

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

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

疑问

$subscribe 监听中 stopWatcher 变量先于定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 const removeSubscription = addSubscription(
   ....
  () => stopWatcher()
)
const stopWatcher = scope.run(() =>{...})

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 使用了 for in 遍历,将获取到原型上方法
for (const key in patchToApply) {
 if (!patchToApply.hasOwnProperty(key)) continue
 const subPatch = patchToApply[key]
 const targetValue = target[key]
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【愚公系列】《循序渐进Vue.js 3.x前端开发实践》064-Pinia 中的一些核心概念
在现代前端开发中,状态管理是确保应用程序高效、可维护的关键因素之一。随着 Vue.js 的普及,Pinia 作为其新一代状态管理库,逐渐成为开发者的热门选择。Pinia 的设计不仅简洁明了,还提供了强大的功能,旨在简化状态管理的复杂性。
愚公搬代码
2025/06/02
310
【愚公系列】《循序渐进Vue.js 3.x前端开发实践》064-Pinia 中的一些核心概念
美团前端vue面试题_2023-05-19
Vue3最重要更新之一就是Composition API,它具有一些列优点,其中不少是针对Options API暴露的一些问题量身打造。是Vue3推荐的写法,因此掌握好Composition API应用对掌握好Vue3至关重要
用户10358241
2023/05/19
1.1K0
简单了解一下pinia的结构
一开始看,是把数据部分变成了 ref,但是仔细一看,原理是toRef。好吧,大概是为了保证响应性,自动结构了。只是还是挺无语的。
用户1174620
2022/05/09
5290
简单了解一下pinia的结构
Pinia入门-实现简单的用户状态管理
在整个应用程序中访问的数据(且不需要被持久化),例如导航栏中显示的用户信息,以及需要通过页面保留的数据,例如一个非常复杂的多步骤表格。
luciozhang
2023/04/22
7750
【vue3】详解单向数据流,大家千万不用为了某某而某某了。
vue的版本一直在不断更新,内部实现方式也是不断的优化,官网也在不断更新。 既然一切皆在不停地发展,那么我们呢?等着官网更新还是有自己的思考? 我觉得我们要走在官网的前面,而不是等官网更新后,才知道原来可以这么实现。。。
用户1174620
2024/08/03
2270
【vue3】详解单向数据流,大家千万不用为了某某而某某了。
pinia
Pinia 介绍: 状态管理工具,代替Vuex 安装: npm install pinia 配置: main.ts: import {createPinia} from 'pinia'//导入 const state = createPinia()// app.use(state)// 初始化仓库 src/store/index.ts import { defineStore } from 'pinia' import { names } from './store_name' export const
kif
2023/02/27
1920
Vue 框架学习系列五:Vue 3 与状态管理库 Pinia 的深度集成
在构建复杂的 Vue.js 应用时,状态管理是一个重要的考虑因素。Vuex 是 Vue.js 官方推荐的状态管理库,但随着 Vue 3 和 Composition API 的发布,一个新的状态管理库 Pinia 开始崭露头角。Pinia 提供了与 Vue 3 和 Composition API 更紧密的集成,同时保持了 Vuex 的核心概念,如 state、mutations、actions 和 getters。本篇文章将探讨如何在 Vue 3 应用中深度集成 Pinia。
china马斯克
2024/10/04
3410
Pinia状态管理器学习笔记,持续记录
官方文档:https://pinia.vuejs.org/ 中文文档:https://pinia.web3doc.top/
房东的狗丶
2023/02/17
1.7K0
Vue项目进阶:再谈Pinia函数式(composition API)用法
Hello大家好,前段时间写了一篇关于Pinia的composition API用法的文章《Pinia进阶:优雅的setup(函数式)写法+封装到你的企业项目》,收到了不少朋友的反馈和建议。笔者也结合最近项目情况和网友们的建议做一次优化,也算是一个比较完整的版本了,这包括:
南山种子外卖跑手
2022/04/21
3.5K0
Vue项目进阶:再谈Pinia函数式(composition API)用法
欧耶!Pinia 正式成为 vuejs 的一员
Pinia 正式成为 vuejs 官方的状态库,意味着 🍍 就是 Vuex 5.x 。 先来看早期 vue 上一个关于 Vuex 5.x 的 RFC : 描述中可以看到,Vue 5.x 主要改善以下几个特性: 同时支持 composition api 和 options api 的语法; 去掉 mutations,只有 state、getters 和 actions; 不支持嵌套的模块,通过组合 store 来代替; 更完善的 Typescript 支持; 清晰、显式的代码拆分; 而 Pinia
码农小余
2022/06/16
6740
欧耶!Pinia 正式成为 vuejs 的一员
vue3了,试试轻量化的Vuex -- Pinia?
Pinia 是 Vue.js 的轻量级状态管理库,最近很受欢迎。它使用 Vue 3 中的新反应系统来构建一个直观且完全类型化的状态管理库。
玖柒的小窝
2021/11/04
1.5K0
vue3了,试试轻量化的Vuex -- Pinia?
Vue3中使用Pinia详解
Pinia是一个专门为Vue.js设计的状态管理库,它提供了一种简单和直观的方式来管理应用程序的状态。在使用Pinia时,可以轻松地创建定义状态的存储,然后将其与Vue组件绑定,使它们能够使用该状态。和上一个博客提到的Vuex相比,Pinia 更加简单易用,体积更小,同时具有更好的 TypeScript 支持和插件系统。
九仞山
2023/10/14
1K0
Vue3之状态管理:Vuex和Pinia,孰强孰弱?
在前端开发中,状态管理器是一种用于管理应用程序全局状态的工具。它通常用于大型应用程序,可以帮助开发者更好地组织和管理状态,并提供一些强大的工具来简化状态的变更和使用。
用户6297767
2023/11/21
2.6K0
Vue3之状态管理:Vuex和Pinia,孰强孰弱?
2021-10-06 vue3-你可能需要pinia来作为状态管理
好久没写博客了,其实我之前和现在写博客的唯一目的,就是为了记录一些不重要,但是以后可能会用但会忘的资料,也就是为了以后方便自己查找。这是其一。
无道
2021/10/08
1.6K0
pinia基本使用介绍
比如开始的时候我们可以使用缓存进行同步数据,虽然很low但是它确实属于一种方案,但是这种方案的实时性很差,也就是很难做到信息的及时同步,虽然你可以写很多监听来达到一个同步的效果,但是代码维护起来就会很笨重
何处锦绣不灰堆
2022/09/21
9110
结合 Vuex 和 Pinia 做一个适合自己的状态管理 nf-state
结合 Vuex 和 Pinia, 保留需要的功能,去掉不需要的功能,修改一下看着不习惯的使用方法,最后得到了一个满足自己需要的轻量级状态管理 —— nf - state。
用户1174620
2022/05/12
9610
结合 Vuex 和 Pinia 做一个适合自己的状态管理 nf-state
Pinia不就是Vuex5?
刚开始使用vue3的时候,全局状态管理器都是使用pinia。还没去了解的时候并不明白为什么要换掉vuex,毕竟是用了好几年的状态管理器,能达到的效果和语法跟vuex几乎一模一样,所以为什么要换?
wade
2022/12/02
5890
Pinia不就是Vuex5?
一文梳理vue面试题知识点
Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。
bb_xiaxia1998
2022/10/24
9770
前端常见vue面试题合集
通过webpack的tree-shaking功能,可以将无用模块“剪辑”,仅打包需要的
bb_xiaxia1998
2022/11/09
7600
使用 Vue 3 与 TypeScript 构建 Web 应用: Todo
引言 界面: Vue.js 3 JavaScript 超集: TypeScript 包管理器: pnpm 前端工程化/打包: Vite 路由: Vue Router 状态管理: Pinia CSS 预处理器: Less 代码格式化: Prettier 代码质量: ESLint 预览
yiyun
2023/07/17
1.2K0
使用 Vue 3 与 TypeScript 构建 Web 应用: Todo
推荐阅读
相关推荐
【愚公系列】《循序渐进Vue.js 3.x前端开发实践》064-Pinia 中的一些核心概念
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验