Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • React

  • Vue

    • vue-demi介绍
    • 使用pinia-plugin-persistedstate如何清除localStorage中的数据
    • script标签的setup实现原理
    • 前端中的拖拽知识
    • rollup-plugin-visualizer
    • Pinia中实现监听action的原理
      • 概述
      • 正文
        • 示例介绍
        • 原理解析
        • 订阅事件$onAction
        • actions属性方法包装
        • actions属性方法的执行
      • 总结
  • JavaScript文章

  • 学习笔记

  • openlayers

  • threejs

  • MapboxGL

  • 工具

  • 源码合集

  • ECMAScript历年新特性

  • 前端
  • Vue
东流
2025-11-28
目录

Pinia中实现监听action的原理

# 概述

在vue3 + Pinia项目中,可以监听store中定义的每个action执行。

# 正文

# 示例介绍

通过一个示例介绍如何监听store中的action。

1、定义一个userStore,如下:

const useUserStore = defineStore('userStore',{
  state:()=>{
    return {
      name:'',
    } 
  }
  actions:{
    setName(name){
      this.name=name;
      return true;
    } 
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13

userStore中的state包含一个name属性,以及一个action``setName方法用于设置name。

2、在组件中监听setName:

const userStore = useUserStore();

useStore.$onAction(({name,after,store,onError,args})=>{
  if(name == 'setName'){
    after((arg)=>{
      if(arg){
        console.log('set name success!')      
      }
    })
  } 
})

// 执行action
userStore.setName('Zhang San')
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在组件中通过$onAction方法监听,$onAction方法接收一个回调函数callback作为参数,callback有四个参数,

  • name:actions中的属性名,即方法名称
  • after:在action调用完成后会触发,其arg参数为action方法的返回值
  • store:即userStore,
  • onError:在action调用时,若出现错误,则会触发
  • args:表示action方法被调用时的参数数组

# 原理解析

# 订阅事件$onAction

store.$onAction(callback)可以理解成订阅store中的所有action事件,当actions中的事件被触发时,都会执行callback回调,而用name可以区分是哪一个方法被触发。

在Pinia中的源码中,使用defineStore定义store时,store就定义了$onAction属性,如下:

const partialStore = {
  $onAction: addSubscription.bind(null,actionSubscriptions)
}

const store = vueDemi.reactive(partialStore);

return store;
1
2
3
4
5
6
7

所以在vue组件中store.$onAction(callback),就相当于执行addSubscription(actionSubscriptions,callback),而addSubscription的实现如下:

const noop = () => { };

function addSubscription(subscriptions, callback, detached, onCleanup = noop) {
    subscriptions.push(callback);
    const removeSubscription = () => {
        const idx = subscriptions.indexOf(callback);
        if (idx > -1) {
            subscriptions.splice(idx, 1);
            onCleanup();
        }
    };
    if (!detached && vueDemi.getCurrentScope()) {
        vueDemi.onScopeDispose(removeSubscription);
    }
    return removeSubscription;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

addSubscription方法就是将监听action的回调callback推入到actionSubscriptions数组中,还定义了清除监听的函数removeSubscription,并且若参数detached不存在或为false,则在作用域销毁时清理,最后返回清除函数。

即每个store中有一个数组actionSubscriptions变量用于存放回调函数callback,当action被触发时就会触发回调。但是,在store的actions中定义事件时,就是很简单地写一个对象的属性方法。这是因为Pinia给这些属性方法重新包裹了一层。

# actions属性方法包装

Pinia会给每一个actions的属性方法进行一层包装,在Pinia中定义了一个action函数,其实现如下:

 const action = (fn, name = '') => {
 // fn:actions中的属性值
 // name:actions中的属性名(即后面用于区分的方法名称)   
        if (ACTION_MARKER in fn) {
        // 判断fn方法是否被标记过,若被标记过,则给其赋值,并返回  
            fn[ACTION_NAME] = name;
            return fn;
        }
        // 定义`wrappedAction`方法
        const wrappedAction = function () {
            setActivePinia(pinia);
            const args = Array.from(arguments);
            const afterCallbackList = [];
            const onErrorCallbackList = [];
            function after(callback) {
                afterCallbackList.push(callback);
            }
            function onError(callback) {
                onErrorCallbackList.push(callback);
            }
            triggerSubscriptions(actionSubscriptions, {
                args,
                name: wrappedAction[ACTION_NAME],
                store,
                after,
                onError,
            });
            let ret;
            try {
                ret = fn.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;
        };
        // 标记方法,表示方法已经被包装过了
        wrappedAction[ACTION_MARKER] = true;
        // 赋值name,用于在wrappedAction中获取name
        wrappedAction[ACTION_NAME] = name;
        // 最后返回`wrappedAction`
        return wrappedAction;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

store中的actions属性对象,会进行for...in遍历,调用action方法进行包装,在action中定义了wrappedAction方法,用于封装属性方法,最后将其返回。

因此在如上组件中调用userStore.setName实际上执行的不是我们所定义的那个setName属性方法,而是经过action封装后的方法。

# actions属性方法的执行

本质上就是执行wrappedAction,其流程如下:

(1)在wrappedAction方法中定义了两个数组:

  • afterCallbackList:fn执行完后需要执行的事件队列
  • onErrorCallbackList:若fn的返回值是Promise对象且运行报错后,需要执行的错误事件队列

并且在wrappedAction中还定义了after和onError方法,分别用于将对应的事件放到队列中。然后会调用triggerSubscriptions方法

function triggerSubscriptions(subscriptions, ...args) {
    subscriptions.slice().forEach((callback) => {
        callback(...args);
    });
}
1
2
3
4
5

(2)triggerSubscriptions方法会去逐个遍历actionSubscriptions数组中的callback,进行调用,即执行store.$onAction(callback)中的callback。callback中的参数after或onError实际上就是在wrappedAction中定义的两个方法,因此在执行过程中,若callback中有after(cb)或onError(cb),则会将cb回调存放对应的队列数组中,以便在fn执行完后使用。 (3)在try...catch中通过apply执行fn方法(即在store的actions中定义的属性方法)得到其返回值ret, (4)若catch捕获到错误,则调用triggerSubscriptions(onErrorCallbackList, error)执行onErrorCallbackList队列中的事件,并返回错误; (5)若ret是一个Promise对象,则执行它,当它被resolve时,调用triggerSubscriptions(afterCallbackList, value),即执行afterCallbackList队列中的事件;若它被reject,则执行onErrorCallbackList队列中的事件; (6)否则调用triggerSubscriptions执行afterCallbackList队列中的事件 (7)最后返回ret

# 总结

store.$onAction的监听原理本质上还是订阅-触发,$onAction就是将整个callback存放在store的actionSubscriptions队列中,而actions中所有的属性方法在定义store时都会被action方法重新包装成wrappedAction;执行wrappedAction时,就会先触发actionSubscriptions中的callback监听,在这个过程中若存在after(cb)或onError(cb),则会将cb放到afterCallbackList或onErrorCallbackList队列中;然后执行fn,在合适的时机调用cb。

编辑 (opens new window)
上次更新: 2025/11/28, 07:31:13
rollup-plugin-visualizer
Map和WeakMap

← rollup-plugin-visualizer Map和WeakMap→

最近更新
01
使用pinia-plugin-persistedstate如何清除localStorage中的数据
11-25
02
patch中的双端比较快速算法
09-25
03
patchDOM元素的更新解析
09-25
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式