videojs v10 源代码系列解读:32 · Reactor 模型:信号驱动的状态机

如何处理动态语言(Python / JS)的“隐形”调用链? 本文揭示动态语言(Python/JS)中静态AST解析导致调用链“断链”的核心问题,提出通过运行时追踪补全图谱的解决方案。基于Code-Graph-RAG的cgr trace机制,分别利用pytest插件与V8 Profiler捕获真实调用路径,将动态边合并至静态图谱,并在权重体系中提升动态边优先级。实践表明,该方法可显著提升图谱覆盖率,实现“结构+行为”双视角理解代码,为MCP与Rerank系统提供可靠支持。 阅读详情

Reactor 是 Actor 的镜像兄弟:由信号驱动而非消息驱动。它的核心是三个概念——monitor(派生状态自动转换)、entry(进状态跑一次)、effects(依赖变化重跑)。实现只有 192 行,但把 28 篇的信号机制用到了极致:所有生命周期都搭载在 effect 的 cleanup 契约上。读完这篇你会理解「薄翻译器」到底薄在哪。

什么时候用 Reactor 而不是 Actor

回忆 27 篇的决策规则:由消息驱动 → Actor;由信号驱动 → Reactor。

具体判断(来自 conventions 文档):一个单元需要 Actor,当它拥有资源 + 串行工作 + 离散事件输入(SourceBuffer:MSE 资源,append/remove 必须串行,输入是离散命令)。一个单元适合 Reactor,当它观察状态变化并做出反应——「源变成了 X,我该发消息告诉 Y」。

设计文档对 Reactor 的定位是薄翻译器:只回答「要不要发消息、发什么消息」,不含业务逻辑。业务逻辑在 behavior 的其他部分或纯函数里。

ReactorDefinition:声明一台反应器

create-machine-reactor.ts 的定义类型:

const def = {
  initial: 'idle',                    // L54
  monitor: [                          // L60 — 单函数或数组
    () => selectStateFromSignals(),   // deriveFn:体内读信号自动成为依赖
  ],
  states: {                           // L66 — Record(不是 Partial!每个状态必须声明)
    idle:    { entry: [...], effects: [...] },
    loading: { effects: [...] },      // 空效果传 {}
  },
};

三个部件的语义(L33-35 文档):

  • monitor——deriveFn 在 tracked effect 中求值;依赖变化重新求值,返回值 ≠ 当前状态时自动 transition。
  • entry——进状态时跑一次,函数体自动 untrack(适合一次性 setup)。
  • effects——进状态时跑,体内 tracked 信号变化时重跑(要排除追踪需手写 untrack())。

注意 L66 的 states: Record<State, ...> 不是 Partial——每个合法状态都必须声明(空效果传 {})。这是刻意为之:漏声明一个状态就漏掉它的行为,类型系统强制完整性。(对比 Actor 的 Partial<Record<...>>——Actor 允许「这个状态不处理任何消息」,Reactor 不允许「这个状态什么效果都没有」不声明。其实语义都允许空,但 Reactor 要求显式写出来。)

实现骨架:descriptors + toEffect(L133-173)

Reactor 的实现思路很统一:把 monitor/entry/effects 全部编译成「descriptor 数组」,再用 28 篇的 effect() 统一执行。

descriptor 结构(L133-137)

type EffectDescriptor = {
  fn: () => void;
  shouldSkip: (snapshot: { value: string }) => boolean;  // 门控:这个状态下要不要跑
  toFnCall?: (baseCall) => EffectCall;                    // 包装(entry 用它加 untrack)
};

descriptors 数组的构建(L148-163)——顺序是保证

const descriptors = [
  // 1. monitor descriptors 排最前(L149-155)
  ...toArray(def.monitor).map((fn) => ({
    fn: () => {
      const target = fn();                                   // tracked 求值 deriveFn
      if (target !== (getState() as State)) transition(target);  // 不等才转
    },
    shouldSkip: isTerminal,                                  // 终态下 monitor 停转
  })),
  // 2. per-state descriptors(L156-162)
  ...Object.entries(def.states).flatMap(([state, stateDef]) => {
    const isNotState = (snapshot) => snapshot.value !== state;   // L157
    return [
      { fn: entry, shouldSkip: isNotState, toFnCall: untracked }, // L159 — entry 加 untrack
      { fn: effect, shouldSkip: isNotState },                     // L160 — effects 不加
    ];
  }),
];

L144-147 的注释明确了这个顺序保证:

monitor descriptors are built first — the ordering guarantee ensures transitions they trigger take effect before per-state effects re-evaluate in the same flush

monitor 在前,per-state effects 在后——同一个 flush 里,monitor 触发的 transition 先生效,per-state effects 再按新状态重求值。如果顺序反了,effects 会用旧状态跑一轮再被新状态重跑,浪费且可能出 bug。

toEffect:统一的执行包装(L165-171)

const toEffect = ({ fn, shouldSkip, toFnCall = (baseCall) => baseCall }) =>
  effect(() => {
    const snapshot = snapshotSignal.get();        // L167 — tracked 读!
    if (shouldSkip(snapshot)) return;              // 门控不通过,跳过
    const baseCall = () => fn();
    return wrapResult(toFnCall(baseCall)());       // cleanup 归一化
  });

L167 是整个 Reactor 的枢纽:snapshotSignal.get() 是 tracked 读(在 effect 的 computed 内),因此每次 transition 都重触发该 effect;shouldSkip 门控决定这个 descriptor 归不归当前状态管。

「进入状态 X 时跑 entry」的机制 = transition 改变 snapshot signal → 所有 descriptor 的 effect 失效重跑 → shouldSkip(isNotState)对 X 的 descriptor 放行、其他跳过 → X 的 entry/effects 执行。「退出状态时清理」= 同一机制:effect 重跑前先执行上次的 cleanup(28 篇 effect 的 L34)。

状态生命周期完全搭载在 effect 的 cleanup 契约上——Reactor 自己没有写一行「退出状态时怎么清理」的代码。这是「薄」的极致。

wrapResult:cleanup 归一化(L125-129)

const wrapResult = (result) => {
  // 函数 → 原样;{abort()} 对象 → 包装成 () => result.abort();falsy → undefined
};

effect 返回 fn | {abort()} | void 三种形态,归一化成 () => void 交给 effect 机制。{abort()} 形态很实用——直接把 Task 的 controller 或 DOM 句柄交出去。

两步销毁(L180-189)

destroy() {
  if (isTerminal()) return;            // 幂等
  transition('destroying');             // L186 — 第一步
  transition('destroyed');              // L187 — 第二步(同步紧跟)
  for (const dispose of effectDisposals) dispose();   // L188 — dispose 所有 effect
}

L183-185 注释解释了为什么两步:先 'destroying' 为将来异步 teardown 预留,随后立即 'destroyed' 覆盖当前的同步场景。目前两步是同步连转——但类型系统里 'destroying' 的存在让未来加异步清理不用改 API。

对比 Actor 的单步 destroy(31 篇)——Reactor 有 effect 需要逐个 dispose(L188),dispose 内部是 watcher.unwatch(c) + 执行最后一次 cleanup(28 篇 effect.ts L39-42)。

一个完整例子:加载分段反应器

用 41 篇会遇到的场景写个概念示例:

createMachineReactor({
  initial: 'idle',
  monitor: [
    () => {
      const mediaSource = state.mediaSource.get();    // tracked 读
      const source = state.source.get();               // tracked 读
      if (!mediaSource || !source) return 'idle';
      return 'active';
    },
  ],
  states: {
    idle: {},
    active: {
      effects: [
        () => {
          // 订问当前时间,决定加载窗口
          const media = context.media.get();
          const unlisten = media.addEventListener('timeupdate', () => {
            loader.send({ type: 'checkBuffer' });
          });
          return unlisten;   // cleanup:退出 active 或依赖变化时自动执行
        },
      ],
    },
  },
});

mediaSource 或 source 变 undefined → monitor 返回 ‘idle’ ≠ ‘active’ → 自动 transition → active 的 effect cleanup 自动跑(解绑 timeupdate)→ idle。整个退出逻辑零手写。

Actor 与 Reactor 对照表

ActorReactor
驱动消息(send)信号(依赖变化)
状态表Partial<Record>Record(必须全声明)
per-state 钩子on(消息→handler)、onSettledentry(一次)、effects(重跑)
context有(双读语义)无(纯 value)
runner可选集成无
destroy单步 'destroyed'两步 'destroying'→'destroyed'
典型用途SourceBufferActor、SegmentLoaderActorloadSegments 的翻译层、track 同步

Reactor 无 context 这个差异值得注意——它真的只是「状态翻译器」,数据都在外部的 composition 信号里(33 篇)。

小结

  • monitor 自动转换——deriveFn tracked 求值,返回值 ≠ 当前状态即 transition。
  • entry untracked / effects tracked——一次性 setup vs 依赖驱动重跑。
  • descriptors 顺序是保证——monitor 先于 per-state effects,同 flush 里 transition 先生效。
  • L167 的 tracked snapshot 读是枢纽——transition 重触发 effect,shouldSkip 门控。
  • cleanup 全搭载 effect 契约——退出状态的清理零手写,「薄」的极致。
  • 两步销毁——为异步 teardown 预留。

下一篇是组合层——Behavior,把信号、Actor、Reactor 全部统一成一种可组合单元。

【Claude Code · VibeCoding】11|需求梳理:云枢智学 / 学员平台 第三章 · 项目实战以「云枢智学」学员平台为例,把业务目标拆成可执行的需求条目。 阅读详情

相关推荐

SpringBoot+Vue+MySQL 高校学科竞赛平台 完整可运行版(源码+文档)

本项目基于SpringBoot+Vue+MySQL构建高校学科竞赛管理平台,覆盖学生、教师、领队教师、管理员四大角色,实现竞赛发布、报名、刷题、评分、公示等全流程闭环管理。系统架构清晰,技术主流,配套完整源码、数据库脚本、万字论文、开题报告、答辩PPT及部署文档,支持一键运行,适用于毕业设计、课程设计及院系二次开发,真正实现“开箱即用”。

招风的黑耳优快云 18

Web 版翻译者资料库

本项目是桌面版《翻译者资料库》的 Web 移植版,基于 Spring Boot 3.3、Thymeleaf、Bootstrap 5.3 与 MySQL 8.0 构建。它面向翻译写作提供资料、词汇、句库、文本工具、对照制作、查找、备份恢复及旧库迁移等功能,完整继承桌面版核心业务规则,支持一键启动与离线访问。

howard2005的专栏 140

SpringBoot+Vue 社区医院信息平台 完整源码+全套文档 开箱即用

本文介绍一套基于SpringBoot+Vue+MySQL的社区医院信息平台,覆盖患者、医生、护士、药品管理员、管理员五大角色,实现从挂号就诊、开方注射、取药结算到数据统计的全流程闭环管理。系统采用成熟技术栈,架构清晰,权限分明,配套完整源码、数据库脚本、万字论文、答辩PPT及部署文档,可直接用于毕业设计或基层医疗信息化落地,助力高效开发与快速交付。

招风的黑耳优快云 282

V8 机制、手写核心函数与 TypeScript 类型体操

或固定初始化(如数组先定长、对象先给全字段)保持类型与形状稳定;这些都比"换框架"更基础。只能处理纯数据(丢函数、Date、RegExp、循环引用报错)。生产级深拷贝要处理:原始值短路、循环引用、V8(Chrome / Node 的 JS 引擎)不是"解释一行跑一行"那么简单,它用。图:V8 执行流程——解释执行 + 热点优化,优化失败会 Deopt 回退。JS 内存自动管理,但"自动"不等于"不会漏"。是"固定间隔最多执行一次"。手写 Promise 是"懂异步"的试金石。是"停下后才执行最后一次",

qq_38468358的博客 346

纯相位全息图优化算法综述

除此之外,还有许多用于减少斑点噪声的非迭代方法,例如:采用下采样掩膜的采样纯相位全息图(Sampled-Phase-only Hologram)方法、模式化相位掩膜(Patterned Phase-Only Hologram)方法、双相位方法(Double-Phase Method),以及使用非随机相位掩膜的方法(Random Phase-Free Method)等。迭代算法通常由一个对目标全息图的近似出发,经过一系列的重复操作不断优化这个近似全息图,直到该近似所得到的重建图像满足一定的误差要求;

ZhongKeWeiXing的博客 1427

博客系统 - 系统简介与首页设计

用Bootstrap实现手机端的博客导航栏效果。

m0_53450409的博客 1237

副队长BootStrap5探究(2)--按钮与按钮组

本文系统讲解Bootstrap 5按钮组件的使用与底层原理。通过CDN引入框架,掌握btn基础类及primary、success等主题色应用,学习轮廓按钮(btn-outline-*)、尺寸控制(btn-lg/sm)、块级布局(d-block)和禁用状态(disabled)等实用功能。结合源码解析,深入理解其基于CSS的圆角、过渡动画、伪类交互实现机制,实现“快速上手+原理掌握”的双重目标,助力前端技能进阶。

编程副队长 517

Cadence元件库介绍

Cadence安装完之后,在安装目录下,会有默认自带的一些常用元件库。例如,我的Candece安装目录为: D:\Cadence\Cadence_SPB_17.2-2016 元件库所在目录为: D:\Cadence\Cadence_SPB_17.2-2016\tools\capture\library 各个元件库对应的内容如下: AMPLIFIER.OLB 共 182 个零件,存放模拟放大器 IC,如 CA3280,TL027C,EL4093 等。 ARITHMETIC.OLB 共 182 个零件,存放

年少时的棉花糖 1万+

React 性能:memo / useMemo / useCallback

讲清 memo/useMemo/useCallback 的配合与边界,先测量再优化,避开常见过早优化。

JudithHuang的博客 325

OpenSees入门指南:从零开始的环境配置与TCL脚本实战

本文提供了一份详细的OpenSees入门指南,涵盖从零开始的环境配置到TCL脚本实战的全过程。文章重点讲解了OpenSees的安装步骤、环境变量设置,并推荐了高效的VS Code集成开发环境配置方法。通过一个简支梁分析的完整TCL脚本示例,手把手引导读者完成第一个非线性有限元分析,帮助土木工程与结构工程领域的研究者与学生快速上手这一强大的开源分析工具。

weixin_28829339的博客 336

解密网页视频Blob链接:从防盗链到流媒体分片技术

本文深入解析了网页视频中Blob链接的工作原理,揭示了其作为现代视频防盗链与流媒体分片技术核心的本质。文章详细阐述了Blob对象与Media Source API如何协同工作,将视频数据分片传输,并结合HTML5的video元素实现安全、流畅的播放体验,有效提升了资源保护能力。

c7d8e的博客 317

DSH (DeepSeek Harness) Windows 升级安装指南20261107

注意:如果在普通窗口执行 nvm 提示找不到命令,或执行 nvm use 提示 Access is denied,请务必关闭当前窗口,重新打开一个管理员CMD 执行上述完整路径命令。找到 .credentials.yaml,将其重命名为 .credentials.yaml.bak(备份并使其失效)。Node.js Storage Location:可改到非系统盘(如 D:\dev\nvm,避免路径含空格或中文)。打开文件资源管理器,进入 C:\Users<你的用户名>.dsh\。

fengye207161的博客 193

NCBI|叶绿体基因组数据上传

叶绿体基因组NCBI上传 小编最近在NCBI上传了好几次数据…想着可以把具体的上传步骤写下来,供大家参考。以下是叶绿体基因组的上传示例,大家可以瞅瞅 图片 准备工作 1.登录NCBI官网:https://www.ncbi.nlm.nih.gov/, 依次点击“sign in to NCBI”——“See more 3rd party sign in options”——搜索“Microsoft live.com”。 ”sign in to NCBI“ ”See more 3rd party sign i

weifanbio的博客 3900

SpringBoot「衣依」服装销售平台:144个Java文件180个接口,从挑衣服到收货评价整条链路都在

# SpringBoot「衣依」服装销售平台:144个Java文件180个接口,从挑衣服到收货评价整条链路都在 > 这是一套基于 Spring Boot 的服装销售平台源码,配套 `db.sql` 建表脚本,业务覆盖商品展示、购物车、订单流转、收货地址、订单评价、商品评论、在线客服、轮播图和资讯。形态是**前后台双端**:前台是顾客逛的商城,后台是管理员管的控制台。本文带你看清里面有什么、怎么跑

面朝大海 226

ZYNQ microblaze boot.bin固化步骤

一,ZYNQ移植软核的原理FPGA规模巨大而且功能复杂,因此设计的每一个部分都从头开始是不切实际的。一种解决的办法是:对于较为通用的部分可以重用现有的功能模块,而把主要的时间和资源用在设计中的那些全新的、独特的部分。这就像是你在开发应用程序的时候就不用直接去写驱动物理硬件的代码,而是直接调用Windows提供的API就行了。

wangjie36的博客 2781

PCIe协议之 Equalization篇 之 理论篇 之 FFE

作为最后一道“精加工”工序,DFE利用其不放大噪声的优势,精准地消除残余的、强烈的后光标ISI,最终得到一个非常干净、张得很开的眼图,供判决器使用。信号经过信道衰减后变得模糊,首先由CTLE进行“粗调”,提供一个宽频带的增益,将眼图初步打开,为后面的DFE创造一个可以正常工作的信号环境。,它通过将输入信号在不同时间点上的多个副本(称为“抽头”)进行加权求和,来直接塑造或“整形”输出信号的波形。:这是最主要的信号路径,通常系数为正值,决定了信号的幅度。:将所有加权后的信号相加,产生最终的均衡输出。

那么菜的博客 753

微信小程序-wxs

在微信开发者工具中右键创建.wxs后缀的文件。每个.wxs文件也是一个独立模块,同样使用导出内容。示例(文件return d;FOO: foo,bar: bar(外联定义参考官方文档)

dreams_dream的博客 342

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

使用echarts模拟甘特图

项目中需按时间段展示不同颜色的数据,通过解析时长并统计各类型总时长,结合预设颜色映射表(含自定义与默认色),动态生成ECharts图表。利用sumByTypeName聚合数据,为每类分配对应颜色,实现图例与柱状图的精准匹配,确保多时段、多类型数据可视化清晰准确。

uuai 28

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

设计模式(unity游戏研发版)

当你使用图形用户界面类时通常会遇到一个问题。比如,你创建了自定义按钮类并允许客户端在按钮中注入自定义代码,这样当用户按下按钮时就会触发这些代码。观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知。你可在按钮中添加订阅机制,允许客户端通过自定义订阅类注入自定义代码。在Unity中,这就像UnityEvent在Inspector中绑定多个回调。

m0_63330263的博客 90

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

【技术专题】JavaScript 网页技术 - JavaScript 流程控制

小锋老师推出《一天学会JavaScript(无废话版)》视频教程,涵盖基础语法、流程控制、函数、数组对象、DOM操作及ES6新特性。内容精炼,配套课件与源码打包下载,助力快速入门。重点讲解if/else、switch、for/while循环、for...of/in遍历及break/continue使用场景,并通过成绩判断实例演示实际应用。适合零基础学习者高效掌握核心技能。

java1234的博客 162

基于 FastAPI+LangChain+LangGraph+Agent+Vue3+MySQL 的房产数据采集与房价预测分析平台

本文介绍一个完整的「房产数据采集+AI房价预测」全栈项目,涵盖FastAPI异步后端、LangGraph Agent智能问答、爬虫采集、机器学习预测与Vue3前端,实现从数据采集到AI交互的闭环。项目融合多技术栈,具备真实业务场景与生产级设计(如降级策略、流式输出、多模型对比),兼具简历亮点与答辩演示优势,可系统性提升工程能力,并附高频面试题解析,是理想的技术综合实践项目。

深耕开发,专注编程实战。分享源码解析、项目搭建、问题排查笔记,沉淀技术,持续更新。 229

货运APP、货运小程序开发:网络货运平台与同城配送搬家系统实战

本文介绍一套完整的货运平台系统,涵盖货运APP、小程序、网络货运、同城配送、搬家预约等场景,基于SpringBoot+MyBatisPlus+MySQL技术栈,采用uni-app实现多端统一开发,配套TMS运输管理与物流管理系统,支持下单、派单、运输、结算全流程,适用于货运搬家、物流搬运与配送业务,可作为物流软件及TMS系统的参考实现。

juzige03的博客 218

Vue3 低代码移动表单设计器实战(二):缩放画布下的拖拽系统与树状级联数据结构

本文深入剖析Vue3低代码表单设计器在缩放画布下的拖拽失效问题,揭示SortableJS在CSS Transform下坐标崩溃的数学根源。通过自研独立视觉克隆壳层与逆向几何补偿算法,实现精准拖拽;采用扁平数据存储+运行时树状计算结构,解决级联依赖与深克隆难题,确保逻辑一致性与性能优化,为复杂低代码系统提供可复用的工程范式。

NanChen的博客 341

LangGraph == 循环结构的实现

这是静态约束,不会参与运行时校验。主要作用是提升代码可读性、通过静态类型检查,同时帮助 LangGraph。也就是说,是否继续重试,在当前实现中主要由大模型决定,而不是由程序逻辑强制保证。因此,如果模型在多次工具调用失败后选择停止重试并生成最终回答,运行图不会阻止它。函数,也不再需要调用 add_conditional_edges()只要大模型持续返回工具调用,流程就会不断回到 llm_node。的缩写,即“推理 + 行动”架构。,运行时只负责判断具体走哪一条路径。

每天都要努力学习哦~~ 347

Vue Router

介绍vue-router的基本使用以及核心概念

k_5412528的博客 154

Vue 前端开发 AI 对话页面:流式消息渲染、文件上传

关键点是不能手动设置 Content-Type 为 multipart/form-data,必须由浏览器自动生成带有 boundary 的请求头,否则后端无法解析。思路:将对象序列化为 JSON 字符串,作为 FormData 的一个普通字段(如 formData.append(‘payload’, JSON.stringify(data))),后端接收该字段后再反序列化。若上传大文件(>10MB),需实现独立的分片上传接口,在提交对话前先获取文件 id 或 url,再随文本发送引用。移除输入框的禁用态;

2402_85729970的博客 388
上一篇: videojs v10 源代码系列解读:31 · Actor 模型:消息驱动的有限状态机
下一篇: videojs v10 源代码系列解读:33 · Behavior:通用组合单元
逻辑玩家
博客等级 码龄19年 75粉丝 108原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值