☰
mobx-state-tree 行动追踪中间件钩子 IActionTrackingMiddleware2Hooks 全面解析:filter/onStart/onFinish 的接口契约与底层实现
2026/10/7 16:03:34 网站建设 项目流程
  • 状态管理
  • 前端

【免费下载链接】mobx-state-tree

Full-featured reactive state management without the boilerplate

项目地址:https://gitcode.com/gh_mirrors/mo/mobx-state-tree
点击查看免费下载

导读

IActionTrackingMiddleware2Hooks是 mobx-state-tree 提供的行动追踪中间件钩子接口,配合createActionTrackingMiddleware2工厂函数使用,可以轻松实现对同步与异步 action(含 flow)从开始到结束的完整生命周期追踪。本文将围绕该接口的三个成员filter、onStart、onFinish,结合 createActionTrackingMiddleware2.ts 源码与 actionTrackingMiddleware2.test.ts 测试,讲解钩子的调用顺序、env/parentCall上下文传播机制,以及如何基于它实现日志、加载态管理、鉴权拦截等实战能力。

接口定义:三个钩子的完整契约

IActionTrackingMiddleware2Hooks定义于 src/middlewares/createActionTrackingMiddleware2.ts:8,是一个泛型接口,接受类型参数TEnv(用于描述通过env在嵌套 action 之间传递的上下文对象类型),默认在createActionTrackingMiddleware2<TEnv = any>中使用。

export interface IActionTrackingMiddleware2Hooks<TEnv> { filter?: (call: IActionTrackingMiddleware2Call<TEnv>) => boolean onStart: (call: IActionTrackingMiddleware2Call<TEnv>) => void onFinish: (call: IActionTrackingMiddleware2Call<TEnv>, error?: any) => void }

三个成员的作用与调用时机如下表:

成员是否可选签名调用时机
filter可选(?)(call) => boolean每个 action 开始前,决定该 action 是否被追踪
onStart必填(call) => void通过filter的 action 开始执行时
onFinish必填(call, error?) => voidaction 结束(成功或失败)时,error携带异常信息

其中call的类型为IActionTrackingMiddleware2Call<TEnv>,它继承IActionContext并额外扩展了两个字段:

export interface IActionTrackingMiddleware2Call<TEnv> extends Readonly<IActionContext> { env: TEnv | undefined readonly parentCall?: IActionTrackingMiddleware2Call<TEnv> }
  • env:当前 action 携带的环境对象(自定义数据,可在父子 action 间共享,初始为undefined);
  • parentCall:当前 action 的父 action 调用上下文(最外层 action 为undefined)。

而IActionContext(见 actionContext.ts:4)提供了六个只读基础字段:name(action 名)、id(事件唯一 id)、parentActionEvent(父 action 事件)、context(被调用节点)、tree(根节点)、args(action 参数数组)。

钩子调用流程:filter → onStart → (嵌套 action)→ onFinish

createActionTrackingMiddleware2的 JSDoc(createActionTrackingMiddleware2.ts:49)给出了明确的调用流程约定:

  • 对每个 action:若filter通过 → 触发onStart→ 递归执行内部嵌套 action → 触发onFinish;
  • 无论 action 是同步还是异步(flow),整体流程保持一致。

以「actiona内部依次调用b1、`b2」为例,事件序列为:

filter(a) onStart(a) filter(b1) onStart(b1) onFinish(b1) filter(b2) onStart(b2) onFinish(b2) onFinish(a)

测试 actionTrackingMiddleware2.test.ts:387 的complete in the expected recursive order用例完整验证了这一顺序:parentAction的onStart先触发,随后childAction1与childAction2各自完成filter → onStart → onFinish,最后parentAction的onFinish兜底收尾。这种「父开始→子进出→父结束」的嵌套结构,使中间件天然支持对 action 调用树的整体观测。

filter:精确挑选被追踪的 action

filter返回true才追踪,返回false则跳过。源码中通过passesFilter决定是否传入钩子(createActionTrackingMiddleware2.ts:94):

const passesFilter = !middlewareHooks.filter || middlewareHooks.filter(newCall) const hooks = passesFilter ? middlewareHooks : undefined

省略filter时默认全部追踪。测试 actionTrackingMiddleware2.test.ts:310 与 actionTrackingMiddleware2.test.ts:343 分别验证了「通过 filter 的trackThisOne会触发 onStart/onFinish」与「未通过 filter 的doNotTrackThisOne完全不触发任何钩子」。

典型用法是按 action 名或参数定向追踪,例如只记录setX:

const mware = createActionTrackingMiddleware2({ filter(call) { return call.name === "setX" && call.args.length > 0 }, onStart(call) { /* ... */ }, onFinish(call, error) { /* ... */ } })

onStart 与 onFinish:成对的生命周期钩子

onStart在 action 真正执行前触发,常用于打点、设置env或开启 loading 状态;onFinish在 action 结束后触发,其第二个参数error?在 action 抛出异常时携带错误对象。源码中RunningAction类负责这一成对逻辑(createActionTrackingMiddleware2.ts:14):构造函数立即调用onStart,finish(error?)方法保证只调用一次onFinish(通过running标志防重入),并透传错误。

class RunningAction { constructor( public readonly hooks: IActionTrackingMiddleware2Hooks<any> | undefined, readonly call: IActionTrackingMiddleware2Call<any> ) { if (hooks) { hooks.onStart(call) } } finish(error?: any) { if (this.running) { this.running = false if (this.hooks) { this.hooks.onFinish(this.call, error) } } } // ... }

成功与失败的对称性在测试中得到印证:同步失败场景(setX内throw "error")下,onFinish收到的error为真,且父子 action 的onFinish都会收到该错误(actionTrackingMiddleware2.test.ts:96)。异常还会沿调用链向上传播——源码中同步 action 抛错时先runningAction.finish(e)再重新throw e(createActionTrackingMiddleware2.ts:102),保证错误不会被中间件吞掉。

注册中间件:addMiddleware 接入模型

IActionTrackingMiddleware2Hooks需要交给createActionTrackingMiddleware2包装成IMiddlewareHandler,再通过addMiddleware(target, handler, includeHooks?)挂载到目标节点上(src/core/action.ts:175)。典型接入方式:

import { addMiddleware, createActionTrackingMiddleware2, types } from "mobx-state-tree" const tracker = createActionTrackingMiddleware2<any>({ filter(call) { return true }, onStart(call) { console.log(`开始执行: ${call.name}`, call.args) call.env = { startedAt: Date.now() } // env 会向下传递给嵌套 action }, onFinish(call, error) { console.log(`结束执行: ${call.name}`, error ? `错误: ${error}` : "成功") } }) const M = types.model({ x: 0 }).actions(self => ({ inc() { self.x++ } })) const store = M.create() addMiddleware(store, tracker, false) // 第二个参数为中间件,第三个参数控制是否包含 hook 事件 store.inc()

env 与 parentCall:父子 action 的上下文桥接

这是IActionTrackingMiddleware2Hooks相比第一代createActionTrackingMiddleware(见 create-action-tracking-middleware.ts,需onResume/onSuspend/onSuccess/onFail五个钩子且以rootId全局查找)更易用的关键设计。中间件在处理"action"事件时构造newCall(createActionTrackingMiddleware2.ts:86):

const newCall: IActionTrackingMiddleware2Call<TEnv> = { ...call, // 浅拷贝父 action 的 env env: parentRunningAction && parentRunningAction.call.env, parentCall: parentRunningAction && parentRunningAction.call }
  • env是父 action 的浅拷贝,子 action 天然继承父级写入的env,在onStart中写入call.env = {...}后,所有后代 action 都能读取到;
  • parentCall直接指向父 action 的call对象,方便通过call.parentCall.name追溯调用来源。

测试 actionTrackingMiddleware2.test.ts:169(对应 issue #1250)演示了filter输出call.parentCall引用:flow 异步执行期间、其他同步 action 穿插执行时,parentCall正确指向发起 flow 的父 action。测试 actionTrackingMiddleware2.test.ts:11 则通过call.env = call.id验证了env在嵌套 action 间的正确复制。

异步支持:基于 RunningAction 的 flow 生命周期追踪

createActionTrackingMiddleware2的名字中「2」即强调对异步流程的增强。与同步 action 的「执行完立即 finish」不同,异步 flow 可能跨多个事件循环周期。源码通过RunningAction上的flowsPending计数器配合四种 flow 事件实现追踪(createActionTrackingMiddleware2.ts:114):

  • flow_spawn:flowsPending++,标记有异步流程派生;
  • flow_resume/flow_resume_error:直接放行(流程恢复执行中);
  • flow_throw:减计数,待 pending 清零后finish(error);
  • flow_return:减计数,待 pending 清零后finish()。

同步 action 执行完后(createActionTrackingMiddleware2.ts:108)若hasFlowsPending为真,则不立即finish,而是等最后一个flow_return/flow_throw到来时统一收尾。测试 actionTrackingMiddleware2.test.ts:113(flow action用例)验证了嵌套 flow 的成功与失败两种路径下,onStart/onFinish仍按「父开始→子进出→父结束」顺序输出。

值得注意的是,mobx-state-tree 官方 API 文档(docs/API/index.md:2231)明确建议:优先迁移到createActionTrackingMiddleware2,因为它更易用。而 docs/overview/utilties.md:20 也将createActionTrackingMiddleware2列为「让追踪异步 action 的中间件编写不再繁琐」的推荐工具。

实战组合:用三个钩子实现 action 日志与加载态

将上述机制组合起来,即可实现一个带嵌套缩进、耗时统计和错误捕获的通用日志中间件:

import { addMiddleware, createActionTrackingMiddleware2 } from "mobx-state-tree" type LogEnv = { depth: number; startedAt: number } const logger = createActionTrackingMiddleware2<LogEnv>({ filter(call) { return true // 追踪所有 action }, onStart(call) { const depth = (call.parentCall && call.parentCall.env ? call.parentCall.env.depth : 0) + 1 call.env = { depth, startedAt: Date.now() } console.log(`${" ".repeat(depth - 1)}→ ${call.name}(${JSON.stringify(call.args)})`) }, onFinish(call, error) { const indent = " ".repeat(call.env!.depth - 1) if (error) { console.error(`${indent}✗ ${call.name} 失败:`, error) } else { console.log(`${indent}← ${call.name} 完成 (${Date.now() - call.env!.startedAt}ms)`) } } }) addMiddleware(store, logger, false)

由于env会从父 action 浅拷贝给子 action(createActionTrackingMiddleware2.ts:90),子 action 总能通过call.parentCall.env读取父级深度,从而实现调用树缩进;onFinish的error参数则让异常路径与成功路径区分清晰。

关联类型与文档索引

IActionTrackingMiddleware2Hooks位于 mobx-state-tree 公开 API 的接口家族中,与本接口直接相关的类型文档包括:

  • IActionTrackingMiddleware2Call:钩子回调的参数类型,含env与parentCall;
  • IActionContext:name/id/args/context/tree/parentActionEvent基础字段来源;
  • IMiddlewareEvent:底层中间件事件类型,含rootId/allParentIds/type等扩展字段;
  • createActionTrackingMiddleware2:接收IActionTrackingMiddleware2Hooks并返回IMiddlewareHandler的工厂函数;
  • 第一代 createActionTrackingMiddleware 与其IActionTrackingMiddlewareHooks(见 create-action-tracking-middleware.ts:5),便于对比迁移。

需要强调的接口约束:onStart与onFinish为必填项,只有filter可省略;onFinish的error参数在 action 正常完成时为undefined,异常终止时携带被抛出的错误值。只要把握「filter 决定是否追踪、onStart 标记开始、onFinish 统一收尾(含异常)」这一契约,即可用极少的样板代码实现健壮的 action 生命周期观测。

  • 状态管理
  • 前端

【免费下载链接】mobx-state-tree

Full-featured reactive state management without the boilerplate

项目地址:https://gitcode.com/gh_mirrors/mo/mobx-state-tree
点击查看免费下载
上一篇:PHP-HTTP Client Common 项目推荐
下一篇:Node-Config终极指南:微服务架构中的分布式配置管理策略 🚀

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询