恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Hyperapp Effects 完全指南:以声明式 Effect 安全封装副作用与外部交互

  • 首页
  • 资讯中心
  • /
  • Hyperapp Effects 完全指南:以声明式 Effect 安全封装副作用与外部交互

相关资讯

使用 Terraform 将 Teleport Database Service 部署到 AWS ECS:基于 IAM Join Token 的完整示例解析 2026/9/20 21:11:17
Windows下Miniconda安装配置指南:从零搭建干净的Python环境 2026/9/20 21:11:16
从零写一个 Stitch 技能:Agent Skills 标准完整实战教程 2026/9/20 21:11:16

最新资讯

FreeRTOS 内核验证怎么做?本地跑通 3 类自动化证明(附 4 个坑点)
AssetRipper:如何快速从 Unity 游戏中提取 3D 模型和纹理资源?完整提取指南
Wox AI 命令完全指南:把保存的 Prompt 变成可复用的查询命令
三个问题定下 YOLO 多目标跟踪部署:BoxMOT 完整跟踪部署实践
R2R本地部署教程:一条命令跑起你的私有AI文档系统
OCT A-SCAN物理建模:从光源频谱到深度标定的全流程仿真

今日推荐

BrewUI:给Homebrew套上图形界面,让macOS软件包管理更简单
BrewUI:让Homebrew包管理变得可视化与高效
公式与文本对齐全攻略:从Word到LaTeX的实用技巧

本周热门

BrewUI:给Homebrew套上图形界面,让macOS软件包管理更简单
BrewUI:让Homebrew包管理变得可视化与高效
公式与文本对齐全攻略:从Word到LaTeX的实用技巧

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

Hyperapp Effects 完全指南:以声明式 Effect 安全封装副作用与外部交互

发布时间:2026/9/20 21:16:18
Hyperapp Effects 完全指南:以声明式 Effect 安全封装副作用与外部交互 前端【免费下载链接】hyperapp1kB-ish JavaScript framework for building hypertext applications项目地址https://gitcode.com/gh_mirrors/hy/hyperapp点击查看免费下载导读本文围绕 Hyperapp 官方架构文档 docs/architecture/effects.md 展开系统讲解 Hyperapp 中effect效果的核心概念、类型签名、定义与使用方式。读完本文你将掌握如何在 action 中把状态迁移与副作用HTTP 请求、DOM 操作、localStorage、WebSocket、自定义事件等解耦组合理解 effecter 的通用化设计原则与浏览器重绘周期同步机制并能够基于仓库源码index.js印证 effect 的底层执行流程为构建纯净、可测试、可复用的 Hyperapp 应用打下基础。一、什么是 Effect副作用的安全表示定义一个effect是 action 用来与某个外部进程交互的表示representation。与 subscriptions订阅 类似effect 用于以安全、纯净、不可变的方式处理与外部世界的不纯异步交互。发起 HTTP 请求、给 DOM 元素设置焦点、把数据写入 localStorage、通过 WebSocket 发送数据……这些都是概念层面上的 effect 例子。关键点在于effect 本身不直接执行副作用代码它只是对要做什么事的一种数据化描述。真正执行副作用的函数被称为effecter见下文第四节。这种描述与执行分离的设计让 action 可以保持为无副作用的纯函数而所有不纯逻辑被集中、可控地隔离在 effecter 中。签名Effect : EffecterFn | [EffecterFn, Payload]即一个 effect 要么直接是一个 effecter 函数要么是一个[EffecterFn, Payload]形式的二元组元组。这种二元组结构被称为effect 描述符。命名规范官方推荐 effect 使用camelCase命名并采用动词如log或动词-名词短语如saveAsPDF的命令式形式。这一约定让代码阅读者一眼就能看出这个 effect 要去执行什么动作与推荐用PascalCase命名的 action参见 actions 文档形成鲜明区分action 是消息effect 是动作。二、在 action 中使用 Effects一个 action 可以将自己的状态迁移与一个或多个 effect关联起来让这些 effect 与状态迁移同时运行。做法是让 action 返回一个带有 effects 的状态数组即 state with effects其中第一项是下一个状态其余各项是要运行的 effects。import { log } from ./fx // Action : (State) - [NextState, ...Effects] const SayHi (state) [ { ...state, value: state.value 1 }, log(hi), log(there), ] // ... h(button, { onclick: SayHi }, text(Say Hi))在这个示例中点击按钮触发SayHiHyperapp 会先应用新的状态{ ...state, value: state.value 1 }然后按数组顺序依次运行log(hi)和log(there)两个 effect。action 当然也可以同时接收 payload 并使用 effects// Action : (State, Payload) - [NextState, ...Effects] const SayBye (state, amount) [ { ...state, value: state.value amount }, log(bye), ] // ... h(button, { onclick: [SayBye, 1] }, text(Bye))这里通过action 描述符[SayBye, 1]把 payload1传给SayByeaction 内部同时完成了状态增量迁移和log(bye)副作用。源码印证effect 的执行顺序从核心实现 index.js 的app()内部 dispatch 代码可以看到这一机制的落地dispatch dispatch((action, props) typeof action function ? dispatch(action(state, props)) : isArray(action) ? typeof action[0] function ? dispatch(action[0], action[1]) : action .slice(1) .map( (fx) fx fx ! true (fx[0] || fx)(dispatch, fx[1]), update(action[0]) ) : update(action) )当 action 返回的是一个首项不是函数的数组时即[NextState, ...Effects]形态先调用update(action[0])应用第一个状态再对action.slice(1)中的每一项 effect 执行(fx[0] || fx)(dispatch, fx[1])。从中可以读出三条关键实现事实state 先于 effects 被应用且 effects 严格按数组顺序运行map顺序每个 effect 都会拿到dispatch与 payload因此 effecter 可以通过dispatch把执行结果回报给应用对应 dispatch.md 中的 DispatchFn 用法表达式fx fx ! true ...表明任何为 falsy 的 effect 会被自动忽略这一点在下一节的条件 effect 中会再次用到同时显式排除了字面量true。三、排除 Effects单元素返回数组与条件 effect如果你在返回数组里不包含任何 effect那么就只有状态迁移发生。下面的OnlyIncrement在行为和使用方式上都与 actions 文档中展示的Incrementactual-state-transition相同// Action : (State) - [NextState] const OnlyIncrement (state) [{ ...state, value: state.value 1 }] // ... h(button, { onclick: OnlyIncrement }, text())这种单元素数组乍看之下有些冗余但当你有一个按条件决定是否运行 effect的 action 时它就派上用场了。例如对比下面这种笨拙的写法const DoIt (state) { let transition { ...state, value: MacGuffin } if (state.eating) { transition [transition, log(eating)] } if (state.drinking) { transition Array.isArray(transition) ? [...transition, log(drinking)] : [transition, log(drinking)] } return transition }MacGuffin 是电影术语指推动剧情但对情节本身无关紧要的事物。与其反复判断transition当前是普通对象还是数组不如始终以数组为起点统一用展开运算符追加const DoItBetter (state) { let transition [{ ...state, value: MacGuffin }] if (state.eating) { transition [...transition, log(eating)] } if (state.drinking) { transition [...transition, log(drinking)] } return transition }坦白说这些示例有些刻意但后者确实复杂度更低。不过针对这类场景还能做得更好——利用任何为 falsy 的 effect 都会被忽略这一事实正是上一节源码中fx fx ! true 所体现的行为const DoItBest (state) [ { ...state, value: MacGuffin }, state.eating log(eating), state.drinking log(drinking), ]当state.eating为false时false log(eating)求值为falsefalsy该 effect 条目被忽略为true时则正常返回log(eating)effect 并运行。这是 Hyperapp 中最优雅、最常用的条件副作用写法。四、定义 Effectseffect 工厂函数从语法上讲一个 effect 采用包含 effecter 及其关联数据的元组形式。技术上说你可以直接使用这个元组但官方推荐用一个函数来创建 effect即工厂函数因为它在如何构建元组这件事上提供灵活性同时整体代码看起来更整洁const massFx (data) [runNormandy, data]massFx是对游戏系列 Mass Effect 的致敬runNormandy则取自玩家飞船 SSV Normandy。这种(payload) [effecter, payload]的工厂模式是整个 Hyperapp 生态中自定义 effect 的标准范式使用方只关心请求做什么调用工厂函数而怎么做完全由 effecter 负责。仓库内的官方包也遵循相同约定例如 packages/events/index.js 中const fx (subscriber) (action) [subscriber, action]即把订阅器 action封装成标准元组。五、Effecters真正执行副作用的函数定义一个effecter是真正去执行某个 effect 的函数。签名EffecterFn : (DispatchFn, Payload?) - void与 subscribers订阅器 一样effecter允许使用副作用也可以手动dispatch一些 action以便把执行过程中的相关结果通知给应用。需要特别强调的是effecter 不只是包装任意不纯代码那么简单。它的目的是成为应用业务逻辑与必须存在的不纯代码之间的通用桥梁。通过尽可能让 effecter 保持通用我们就能在被请求做什么与该请求如何被执行之间形成干净、可管理的分离。5.1 反例与业务耦合的 effecter先看一个不符合规范的 effecter// This effecter is ill-formed. const runHarvest (dispatch, _payload) { const tiberium document.getElementById(tiberium) dispatch((state) ({ ...state, tiberium })) }Tiberium 出自游戏 Command Conquer 系列是一种可采集的有毒外星晶体。它确实能运行但问题很明显它耦合到了应用的状态结构直接以(state) ({ ...state, tiberium })修改特定字段被引用的元素 IDtiberium被硬编码在函数内部。5.2 逐步重构让 effecter 通用化第一步利用可以给 effecter 传 payload的能力把回调 action从 effecter 中解耦出来const runHarvest (dispatch, payload) dispatch(payload.action, document.getElementById(tiberium))第二步进一步利用 payload 传入 effecter 工作所需的数据元素 IDconst runHarvest (dispatch, payload) dispatch(payload.action, document.getElementById(payload.id))第三步把函数名也改成能体现其通用性质的名字const runGetElement (dispatch, payload) dispatch(payload.action, document.getElementById(payload.id))现在runGetElement完全不关心这个元素 id 属于哪个业务、也不关心拿到的元素会被哪个 action 消费——它成了一个可以复用于任何按 id 取元素并交给某个 action场景的通用 effecter。这正是文档反复强调的原则一个合格的 effecter 应当尽可能通用。5.3 同步与 Hyperapp 重绘周期对齐如果一个 effecter 运行了某个异步操作并希望把操作结果回报给应用那么它必须确保通信 dispatch 的时机与 Hyperapp 的重绘周期对齐。这一点很重要因为只有对齐才能保证状态被正确设置。Hyperapp 的重绘周期与浏览器自身的自然重绘周期保持同步因此异步 effecter 也必须这样做。首选方式是用requestAnimationFrame()如果该方法不可用则退而求其次使用setTimeout()。先看一个不符合规范的异步 effecter// This effecter is ill-formed. const runBrotherhood async (dispatch, payload) { const response await fetch(payload.lookForKaneHere) const kaneLives response.json() requestAnimationFrame(() { dispatch((state) ({ ...state, message: kaneLives ? One vision! One purpose! : , })) }) }Kane 是 Command Conquer 系列中 Nod 兄弟会的领袖其经典台词是 One vision! One purpose!。它的问题依旧在于dispatch 的内容内联耦合了业务状态结构message字段、硬编码文案无法复用。再看一个更规范的异步 effecterconst runSimpleFetch async (dispatch, payload) { const response await fetch(payload.url) requestAnimationFrame(() dispatch(payload.action, response.json())) }这个版本把请求哪个 URL拿到数据后派发哪个 action全部交给 payload 决定effecter 本身只负责发起fetch→ 等响应 → 在下一个requestAnimationFrame回调中把response.json()作为 payload 派发给payload.action。于是runSimpleFetch成了通用的fetch 并回传数据 effecter可在任何需要 HTTP 请求的业务场景中复用。5.4 源码印证重绘周期的实现在核心实现 index.js 中update()正是通过requestAnimationFrame调度渲染的var update (newState) { if (state ! newState) { if ((state newState) null) dispatch subscriptions render id if (subscriptions) subs patchSubs(subs, subscriptions(state), dispatch) if (view !busy) requestAnimationFrame(render, (busy true)) } }可以看到状态更新后渲染通过requestAnimationFrame(render, ...)排入浏览器的下一个绘制帧并用busy标志防止重复排队。因此异步 effecter 若在requestAnimationFrame回调中 dispatch 结果就能保证该 dispatch 的状态变更恰好落在 Hyperapp 的渲染周期内被正确处理避免出现状态已更新但视图/订阅未同步的时序问题。六、实战用自定义 Effect 与旧应用通过 CustomEvent 通信使用自定义 effect 的理想场景之一是让 Hyperapp 应用与某个旧版legacy应用通过自定义事件CustomEvent通信。我们可以让 Hyperapp 应用用一个自定义 effect 来触发自定义事件。先定义 effect 工厂与 effecter例如放在./fx.js// ./fx.js const runEmit (_dispatch, payload) dispatchEvent(new CustomEvent(payload.type, { detail: payload.detail })) export const emit (type, detail) [runEmit, { type, detail }]注意这里runEmit甚至不需要dispatch用_dispatch占位因为发出事件本身就是目的不需要向应用回报结果。emit(type, detail)工厂函数则把两个参数打包进{ type, detail }payload交给runEmit。然后在应用中使用import { h, text, app } from hyperapp import { emit } from ./fx app({ view: () h(main, {}, [ h( button, { onclick: (state) [ state, emit(outgoing, { message: hello }) ], }, text(Send greetings) ), ]), node: document.querySelector(main), })点击 Send greetings 按钮时内联 action 返回[state, emit(outgoing, { message: hello })]状态保持不变同时触发名为outgoing、detail为{ message: hello }的自定义事件。旧版应用只需监听该事件即可接收到数据从而实现跨框架通信。延伸阅读如果你需要的是监听外部事件而不是发出事件Hyperapp 对应的方案是自定义 subscription订阅器。两者方向互补effect 用于向外部发出/执行subscription 用于从外部接收。完整对比可参考 subscriptions.md。此外effecter 通过dispatch回报结果的完整语义递归 dispatch、自定义 dispatch 增强见 dispatch.md。七、在应用初始化时使用 Effectseffect 不仅可以在事件触发的 action 中使用也可以配合app()的init:属性在应用启动时运行。init:支持四种形式之一直接设置状态、[state, ...effects]、action、[Action, payload]。其中状态 effects形式尤其适合做启动阶段的加载逻辑app({ init: [ { loading: true }, log(Loading...), load(myUrl?init, DoneAction), ], // ... })这印证了 state.md 的 State With Effects 一节 所述规则只要用数组设置状态Hyperapp 就会把它解释为第一个条目是状态其余条目是待运行的 effects并保证先应用状态再按顺序运行 effects。因此如果你真的想用数组作为状态本身就必须把它再包一层[[a, b, c]]对应的 action 侧写法与注意事项见 actions.md 的 Transitioning Array State。八、总结围绕 docs/architecture/effects.md 的核心脉络我们可以提炼出 Hyperapp effect 体系的几个要点关注点结论本质effect 是副作用的表示而非执行执行者是 effecter签名Effect : EffecterFn \| [EffecterFn, Payload]effecter 签名为(DispatchFn, Payload?) - void使用方式action 返回[NextState, ...Effects]状态先行、effects 按序执行条件 effect依赖falsy effect 被忽略的机制用state.cond fx(...)即可优雅实现设计原则effecter 应尽可能通用用 payload 传入动作/数据与具体业务解耦异步同步异步 effecter 回报结果须在requestAnimationFrame退化方案setTimeout回调中 dispatch与 Hyperapp 重绘周期对齐典型场景HTTP 请求、DOM 焦点、localStorage、WebSocket以及与 legacy 应用的 CustomEvent 双向通信这套纯 action 数据化 effect 通用 effecter的架构让业务逻辑与不纯代码的边界清晰可管理正是 Hyperapp 在 1kB 量级下依然能保持可测试性与可维护性的关键设计之一。如需继续深入可阅读与之配套的 actions、subscriptions、dispatch、state 与 views 文档或在 tests/index.test.js 与 index.js 中查看实际行为验证。赞分享前端【免费下载链接】hyperapp1kB-ish JavaScript framework for building hypertext applications项目地址https://gitcode.com/gh_mirrors/hy/hyperapp点击查看免费下载相关推荐Flame Effects 特效系统完全指南从声明式动画到自定义 EffectFlame Effects 特效系统完全指南从声明式动画到自定义 Effect Flame 作为一款基于 Flutter 的游戏引擎其 Effects特效游戏开发图形学redux-saga 的 put Effect 完全指南以声明式方式向 Store 分发 Actionredux saga 的 put Effect 完全指南以声明式方式向 Store 分发 Action 导读 在 redux saga 中Saga 需要把异前端告别副作用地狱Lustre框架中的声明式副作用管理全景指南告别副作用地狱Lustre框架中的声明式副作用管理全景指南 为什么你的前端应用需要副作用防火墙 当你在传统前端框架中编写如下代码时是否意识到自己正踏入上一篇Blockly与三大前端框架集成深度对比React vs Vue vs Angular终极指南下一篇Vue Naive Admin常见问题解决方案10个开发者必知技巧创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号