手撕pinia源码(三):实现pinia内置方法

手撕pinia源码目录

手撕pinia源码(一): pinia的使用

手撕pinia源码(二):实现defineStore

手撕pinia源码(三):实现pinia内置方法

源码地址传送门

$patch实现

对当前状态应用状态补丁。可以同时批量设置状态值,传入一个回调函数,回调函数接收一个store参数可访问当前仓库的所有属性方法。 $patch有两种使用方式

js 复制代码
import { useCounterStore } from './stores/counter';
const store = useCounterStore();
store.$patch({counter: 1000})
// or
store.$patch((data) = {
   data.counter = 1000;
})

代码编写

js 复制代码
// store.js
function isObject(val) {
    return typeof val === 'object' && val !== null;
}

function mergeReactiveObject(target, state) {
    for (let key in state) {
        let oldValue = target[key];
        let newValue = state[key];
        if (isObject(oldValue) && isObject(newValue)) {
            target[key] = mergeReactiveObject(oldValue, newValue);
        } else {
            target[key] = newValue;
        }
    }
    return target;
}
function createSetupStore(id, setup, pinia, isOptions) {
    function $patch(partialStateOrMutatior) {
        // 判断入参类型
        if (typeof partialStateOrMutatior === 'object') {
            mergeReactiveObject(pinia.state.value[id], partialStateOrMutatior);
        } else {
            partialStateOrMutatior(pinia.state.value[id]);
        }
    }

    const partialStore = {
        $patch
    }
    const store = reactive(partialStore);
    ...代码省略
}

修改createSetupStore方法。

  • 代码26行,实现pinia内置方法
  • 代码19行,实现$patch方法
    • 代码21行,判断入参为object,则是第一种用法,那么需要将两个对象合并即可,
      • mergeReactiveObject就是简单的递归对象合并
    • 代码23行,如果不是对象,则是function,此时只需要执行用户传入的函数,并且获取到当前的store返回给用户,让用户操作即可,pinia.state.value[id]ref响应式对象,所以用户可以直接修改就能触发响应。

$reset

通过构建新的状态对象,将存储重置为初始状态。此API只能用于通过options方式构建的store,后面会讲解为何只有options方式构建的store才支持此API。

代码编写

js 复制代码
// store.js
function createOptionsStore(id, options, pinia) {
    ...代码省略
    
    const store = createSetupStore(id, setup, pinia, true); 
    store.$reset = function () {
        const newState = state ? state() : {};
        store.$patch((state) => { 
            Object.assign(state, newState);
        })
    }
    return store;
}
  • 代码6行,给createOptionsStore构建的store添加一个$reset方法
    • 代码7行,重新调用用户定义的state函数,即可获取初始值,所以$reset方法只能用在options定义的store中,因为如果是setup形式的话,我们无法获取到起初始值是什么。 -代码8行,直接调用我们刚刚编写的$patch函数,用初始状态覆盖掉当前状态即可。

$subscribe

设置一个回调,以便在状态更改时调用。它还返回一个函数来删除回调。

代码编写

js 复制代码
// store.js
function createSetupStore(id, setup, pinia, isOptions) {
    function $subscribe(callBack) {
        watch(pinia.state.value[id], (state) => {
            callBack({ storeId: id }, state)
        });
    }
    const partialStore = {
        $patch,
        $subscribe
    }
    ...代码省略
}
  • 代码10行,添加一个$subscribe内置方法
    • 代码3行,实现subscribe方法,
    • 代码4行,代码非常简单,调用vuewatchAPI,监听当前的store属性的变化,当属性变化时,调用用户传递的callBack函数,组装好用户需要的参数返回即可。

$onAction

设置一个回调函数,每次调用操作时执行此回调函数,并且回调函数接收一个参数,参数包含所有操作信息 ,$onAction的使用示例

js 复制代码
import { useCounterStore } from './stores/counter';
const store = useCounterStore();
store.$onAction(function ({after, onError}) {
    after((result) => { // 修改后的结果 })
    after((result) => { // 修改后的结果 })
    after((result) => { // 修改后的结果 })
    onError((error) => { // 执行错误 })
})

观察上面代码,after是可以被多次调用的,我们可以发现,这种形式就是利用发布订阅模式,缓存所有的订阅函数,当调用时执行这些订阅函数。

代码编写

js 复制代码
// 新建一个subscribe.js
export function addSubscription(subscriptions, callback) {
    subscriptions.push(callback);
    const removeSubscription = () => {
        const idx = subscriptions.indexOf(callback);
        if (idx > -1) {
            subscriptions.splice(idx, 1);
        }
    }
    return removeSubscription;
}

export function triggerSubscriptions(subscriptions, ...args) {
    subscriptions.slice().forEach(cb => cb(...args));
}
js 复制代码
// store.js
import { addSubscription, triggerSubscriptions } from './subscribe';
let actionSubscriptions = [];

const partialStore = {
    $patch,
    $subscribe,
    $onAction: addSubscription.bind(null, actionSubscriptions)
}
...代码省略
function warpAction(name, action) {
        return function (callBack) {
            const afterCallbackList = [];
            const onErrorCallbackList = [];
            function after(callBack) {
                afterCallbackList(callBack);
            }
            function onError(callBack) {
                onErrorCallbackList(callBack);
            }
            triggerSubscriptions(actionSubscriptions, {after, onError});
            try {
                let res = action.apply(store, arguments);
                triggerSubscriptions(afterCallbackList, res)
            } catch(error) {
                triggerSubscriptions(onErrorCallbackList, error);
            }

            if (res instanceof Promise) {
                return res.then((value) => {
                    triggerSubscriptions(afterCallbackList, value)
                }).catch(error => {
                    triggerSubscriptions(onErrorCallbackList, error);
                })
            }
            return res;
        }
    }
    ... 代码省略
  • 代码subscribe.js是一个经典的发布订阅模式,如果不了解发布订阅模式可以看我另一篇文章有详细解读。传送门
  • 代码8行,定义一个$onAction的内置方法,当调用这个内置方法的时候,会执行订阅函数收集起来。
  • 代码11行,修改warpAction函数,在调用方法时执行此函数
    • 代码13行,分别定义afterCallbackListonErrorCallbackList,因为afteronError是可以被多次调用的,所以也是一个发布订阅模式
    • 代码15行,提供after方法,当用户调用after时,将参数存入afterCallbackList,代码18行同理。
    • 用户调用action时,触发triggerSubscriptions发布所有的onAction订阅者,入参包含afteronError提供给用户使用。
    • 代码23行,执行完action后,触发triggerSubscriptions发布给所有的after订阅者,入参为最新的状态值。
    • 代码25行,同理,当执行action报错,触发triggerSubscriptions发布给所有的onError订阅者,入参为报错信息。
    • 代码29行,因为action是允许写异步方法的,所以有可能action的执行结果返回的是一个promise,所以判断其是否是Promise,如果是则调用起then方法等待执行完毕后再通知after订阅者,同上。

结束

总结手撕pinia源码系列技术文章,我如果有不懂的,欢迎留言或移步至文章开头查看完整源码,跟着逻辑打印一遍就会觉得pinia非常简单们详细剖析了pinia源码,从源码的角度实现了一个简单版的pinia,当然还有很多特殊场景没有处理,但是能够理解pinia核心我认为就足够了,主要学习的是其设计思想,最后如果有不懂的,欢迎留言或移步至文章开头查看完整源码,跟着逻辑打印一遍就会觉得pinia非常简单。

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax