Ember.js 中的 @glimmer/component 完全指南:Glimmer 组件 API、args 与组件管理器源码解析

发布时间:2026/9/20 14:46:43
Ember.js 中的 @glimmer/component 完全指南:Glimmer 组件 API、args 与组件管理器源码解析 前端Web框架UI组件【免费下载链接】ember.jsEmber.js - A JavaScript framework for creating ambitious web applications项目地址https://gitcode.com/gh_mirrors/em/ember.js点击查看免费下载导读glimmer/component是 Ember Octane3.15及之后版本中组件Component的默认 JavaScript 基类它为模板 可选 JS 类的组件模型提供了最小而精确的 API。本文以 packages/glimmer/component/README.md 为核心结合仓库内该包的完整源码系统讲解glimmer/component的安装方式、Template-only 组件、block块体系、args、constructor、willDestroy等生命周期 API并深入剖析其背后的Args类型系统与组件管理器Component Manager实现帮助你既会用、又懂原理。一、glimmer/component 是什么在 Ember 的组件体系中组件被划分为两类Template-only仅模板组件只有模板、没有 JavaScript 类带 JavaScript 的组件由模板加一个支撑类backing class组成。Ember 提供两种 JavaScript 组件类Glimmer 组件从glimmer/component导入是 Ember Octane3.15及更新版本edition的默认组件Classic 组件从ember/component导入是 3.15 之前旧版 Ember 的默认组件。这一区分在包源码的文档注释中有明确说明见 src/index.ts。同时值得注意的是Ember 6.8 之前组件默认以成对的.hbs与.js文件编写现在默认的编写格式是.gjs即 template tag但成对文件方式依然被支持。本包当前版本为2.1.1见 package.jsonengines要求 Node.js 18是一个 V2 格式的 Ember 插件addon。二、安装在任意 Glimmer 应用包括 Ember.js 应用中将其作为开发依赖安装npm install --save-dev glimmer/component从 package.json 可以看到运行时它只依赖embroider/addon-shim^1.10.2这是一个用于将 V2 addon 以 V1 兼容方式暴露给宿主应用的垫片shim。包的入口通过addon-main.cjs调用addonV1Shim(__dirname)完成这一适配见 addon-main.cjs。包同时发布了development与production两套产物见 package.json 的exports字段配合glimmer/env的DEBUG标志用于在开发模式下注入额外的断言检查在生产构建中剔除。三、基础用法继承 Component在 Glimmer 应用或使用 Glimmer 语法的 Ember 应用中使用该包的最基本方式是导入默认导出并继承import Component from glimmer/component; export default class MyComponent extends Component { get doubled() { return this.args.foo * 2; } template {{foo}} * 2 {{this.doubled}} /template }要点this.args父组件传入的命名参数对象在 JS 侧通过this.args.foo访问foo同一参数在模板侧的写法前缀表示参数argumenttemplateGlimmer 的模板标签template tag可以把模板与 JS 类写在同一个.gjs/.gts文件里。参数与属性的区别在模板中以开头的都是来自父组件的参数例如{{firstName}}不带的则是当前组件类上定义的属性例如{{name}}。在 JS 侧参数统一收敛到this.args——如果模板中{{firstName}}的值是Tom那么组件内this.args.firstName同样为Tom。这一约定在 src/-private/component.ts 的类文档中有完整说明。参数与属性是两类不同的数据参数由父组件传入属性由组件自身定义。例如组件内可以定义自己的user属性再传给子组件UserGreeting greetingHello name{{user.name}} /四、Template-only 组件与文件系统约定4.1 定义一个 Template-only 组件最简单的组件就是一个只含template的.gjs文件放在app/components目录下// app/components/person-profile.gjs template h1{{person.name}}/h1 img src{{person.avatar}} p classsignature{{person.signature}}/p /template随后在应用的其他模板中以大写开头的方式调用它大写用于与原生 HTML 元素区分// app/templates/application.gjs import PersonProfile from ../components/person-profile; template PersonProfile person{{model.currentUser}} / /template渲染结果大致为h1Tomster/h1 img srchttps://emberjs.com/tomster.jpg p classsignatureOut of office this week/p4.2 文件系统嵌套与显式导入在 template tag 格式下模板中可被调用的可调用对象invokables包括组件、helper、modifier必须先通过import显式导入这有助于理解值的来源也为构建期优化tree-shaking 等提供空间。由于组件是通过 ES module 导入的因此它们可以放在项目的任意路径但按惯例可复用组件放在app/components路由组件Route components放在app/templates且必须与路由同名——例如Person路由对应app/templates/person.gjs用户访问/person时该组件即被渲染。五、Block 体系yield、命名块与 has-block组件模板内部的{{yield}}用于插入调用方传入的 block 内容。假如组件内加入// app/components/person-profile.gjs template h1{{person.name}}/h1 {{yield}} /template调用方在开闭标签之间传入的内容会在{{yield}}所在位置被渲染PersonProfile person{{model.currentUser}} pAdmin mode/p /PersonProfile重要语义block 在其定义处的作用域中执行即 block 内可以访问其定义位置的变量与作用域。5.1 向 block 传递参数{{yield}}支持位置参数block 通过as |name|接收// 组件模板 template h1{{person.name}}/h1 {{yield person.signature}} /template// 调用方 PersonProfile person{{model.currentUser}} as |signature| {{signature}} /PersonProfile5.2 传递多个命名 block通过给{{yield}}加to参数可以定义命名块此时所有 block 都必须显式命名包括default块不带to的{{yield}}对应default块调用方不写:name时传入的就是 default 块// 组件模板 template h1{{yield totitle}}/h1 {{yield}} /template// 调用方 PersonProfile person{{model.currentUser}} :title{{model.currentUser.name}}/:title :default{{model.currentUser.signature}}/:default /PersonProfile命名块同样可以接收参数// 组件模板 template h1{{yield person.name totitle}}/h1 {{yield person.signature}} /template// 调用方 PersonProfile person{{model.currentUser}} :title as |name|{{name}}/:title :default as |signature|{{signature}}/:default /PersonProfile5.3 检查 block 是否存在使用(has-block)关键字可以判断调用方是否传入了某个 block从而在使用传入 block与渲染默认内容之间做条件选择template h1 {{#if (has-block title)}} {{yield person.name totitle}} {{else}} {{person.name}} {{/if}} /h1 {{#if (has-block)}} {{yield person.signature}} {{else}} {{person.signature}} {{/if}} /template(has-block)不带参数时检查 default 块带字符串参数时检查对应的命名块。由此调用方可以自由选择传两个块、只传 title 块、只传 default 块或什么都不传{{! 两个块都传 }} PersonProfile person{{model.currentUser}} :title as |name|{{name}}/:title :default as |signature|{{signature}}/:default /PersonProfile {{! 只传 title 块 }} PersonProfile person{{model.currentUser}} :title as |name|{{name}}/:title /PersonProfile {{! 只传 default 块 }} PersonProfile person{{model.currentUser}} as |signature| {{signature}} /PersonProfile {{! 不传任何块 }} PersonProfile person{{model.currentUser}}/5.4 检查 block 是否带参数(has-block-params)用于判断 block 是否声明了参数从而决定{{yield}}时是否附带值template {{#if (has-block-params)}} {{yield person.signature}} {{else}} {{yield}} {{/if}} /template六、用 JavaScript 定制组件在.gjs文件中继承glimmer/component即可为组件添加自己的属性、方法、getter 与生命周期钩子模板中通过{{this}}引用组件实例// app/components/person-profile.gjs import Component from glimmer/component; export default class PersonProfile extends Component { get displayName() { let { title, firstName, lastName } this.args.person; if (title) { return ${title} ${lastName}; } else { return ${firstName} ${lastName}; } } template h1{{this.displayName}}/h1 {{yield}} /template }6.1constructor(owner, args)构造函数在组件每次创建新实例时执行用于初始化组件状态。签名参数为owner对象与args对象且必须先调用super(owner, args)可直接传...argumentsimport Component from glimmer/component; export default class SomeComponent extends Component { constructor(owner, args) { super(owner, args); if (this.args.displayMode list) { this.items []; } } }服务注入service injection与参数args在构造函数中均已可用import Component from glimmer/component; import { service } from ember/service; export default class SomeComponent extends Component { service myAnimations; constructor(owner, args) { super(owner, args); if (this.args.fadeIn true) { this.myAnimations.register(this, fade-in); } } }6.2willDestroywillDestroy在组件已从 DOM 中移除、但尚未完全销毁时被调用适合做清理工作import Component from glimmer/component; import { service } from ember/service; export default class SomeComponent extends Component { service myAnimations; willDestroy() { super.willDestroy(); this.myAnimations.unregister(this); } }官方建议如果只想做与销毁相关的资源清理也可以优先考虑ember/destroyable提供的 API。6.3argsargs是包含传入参数的只读对象。例如调用SomeComponent fadeIn{{true}} /组件内收到的args为{ fadeIn: true }关键特性在组件的整个生命周期包括constructor与willDestroy中均可访问argsargs会被自动标记为 tracked 属性可像其他 tracked 属性一样被依赖并自动更新模板中通过前缀访问this.args.fadeIn对应模板里的fadeIn。从源码看args在基类构造函数中被直接赋值并声明为readonly args: ReadonlyArgsS见 src/-private/component.ts其 tracked 化由渲染引擎在创建实例时完成。6.4isDestroying与isDestroyedisDestroying布尔标志组件正在销毁过程中时为true在willDestroy被调用之前置位isDestroyed布尔标志组件已完全销毁时为true在willDestroy被调用之后置位。两者的实现位于 src/-private/component.ts通过模块内部的WeakMapDESTROYING/DESTROYED记录状态由组件管理器在销毁流程中写入见下文第七节。七、源码原理Args 类型系统与组件管理器7.1 组件的类型签名与Args推导ComponentS unknown是泛型类类型参数S用于描述组件的签名signature。仓库中的类型工具位于 src/-private/component.tsEmptyObject一个带唯一 symbol 键的空对象类型用于在组件没有命名参数时触发 TypeScript 的多余属性检查excess property checking——如果某个组件未声明任何命名参数却被传入参数会得到类型错误。这是为了避免 TS 对{}的宽松处理ArgsForS/_ExpandSignatureT/ExpandSignatureT将各种简写形式的签名例如直接写{ Named: ...; Positional: ... }长格式或直接写一个对象当作 Named 参数统一脱糖为完整的{ Element, Args, Blocks }结构ArgsS最终对外暴露的命名参数类型即ExpandSignatureS[Args][Named]。ExpandSignature还特意处理了联合类型的分配问题conditional type 的 distributive 行为保证联合类型签名不会误入 legacy 分支。7.2 基类GlimmerComponentGlimmerComponentSsrc/-private/component.ts是真正的实现类构造函数接收(owner, args)在DEBUG模式下会校验owner是对象且args已通过ARGS_SET登记否则抛出错误You must pass both the owner and args to super() in your component: ...。这解释了为什么用户代码必须调用super(owner, args)初始化DESTROYING/DESTROYED两个 WeakMap 中的状态为false提供空的willDestroy(): void {}供子类覆写。对外导出的Component类src/index.ts继承自GlimmerComponent在构造函数中同样带有 DEBUG 断言并调用setOwner(this, owner)将 owner 注册到组件实例上——这也是组件内能使用服务注入的原因之一。7.3 组件管理器创建、销毁与调度glimmer/component本身并不直接渲染组件而是通过组件管理器Component Manager协议接入 Ember 的渲染引擎注册模块加载时调用setComponentManager((owner) new GlimmerComponentManager(owner), Component)见 src/index.ts把组件类与它的管理器绑定。setComponentManager与capabilities均由ember/component导出见 packages/ember/component/index.ts也就是说任何类不限于继承Component都可以通过这一 API 声明自己由某个管理器驱动。能力声明ember-component-manager.ts 中声明了capabilities(3.13, { destructor: true, asyncLifecycleCallbacks: false, updateHook: false })告诉渲染引擎本管理器支持销毁钩子、不支持异步生命周期回调、不需要 update 钩子。创建base-component-manager.ts 的createComponent(ComponentClass, args)在 DEBUG 下先将args.named登记进ARGS_SET再new ComponentClass(this.owner, args.named)实例化组件getContext直接返回组件实例作为模板上下文。销毁EmberGlimmerComponentManager.destroyComponentember-component-manager.ts先置位isDestroying然后通过ember/runloop的schedule分两个队列执行先schedule(actions, component, component.willDestroy)调用willDestroy钩子再schedule(destroy, this, scheduledDestroyComponent, component)执行真正的销毁destroy(component)并置位isDestroyed。scheduledDestroyComponent中还会通过isDestroyed做幂等保护。这套基类 管理器的分层设计使得glimmer/component可以同时服务于 Ember.js 渲染环境其销毁流程与 Ember 的 runloop、ember/destroyable紧密协作。八、版本与演进从 CHANGELOG.md 可以梳理出该包的关键演进2.0.02024-10-29破坏性变更——转换为 V2 addon放弃对ember 4.10的支持2.1.02026-04-07将默认导出由GlimmerComponent重命名为Component更新willDestroy相关文档移除参数说明2.1.12026-04-07改为使用 pnpm 发布bugfix。更早的 1.x 及 2.0.0-beta 版本曾维护在独立的 glimmer.js 仓库中。结合当前仓库的 tsconfig.jsonstrict: true、moduleResolution: node、输出声明到./dist本包以严格 TypeScript 编写并发布类型声明typesVersions与exports.types保证消费端包括 Ember 应用与 Glint 类型检查都能解析到正确的类型。九、总结glimmer/component是 Ember 现代组件模型的基石其 API 刻意保持精简模板侧args、{{yield}}含命名块与参数、(has-block)、(has-block-params)类侧constructor(owner, args)、args、willDestroy、isDestroying、isDestroyed架构侧Component基类通过setComponentManager与capabilities(3.13)接入渲染引擎由BaseComponentManager负责实例化、由EmberGlimmerComponentManager借助 runloop 调度销毁。理解这份源码不仅能让你在 Ember 应用中熟练编写 Glimmer 组件也能为阅读ember/component、Glimmer VM 的渲染管线打下基础。相关实现文件均可在仓库中继续深入阅读组件基类与类型工具、组件管理器基类、Ember 环境组件管理器、对外入口。赞分享前端Web框架UI组件【免费下载链接】ember.jsEmber.js - A JavaScript framework for creating ambitious web applications项目地址https://gitcode.com/gh_mirrors/em/ember.js点击查看免费下载相关推荐Ember.js Glimmer 嵌入 API 实战在自定义宿主中集成多个全局组件Ember.js Glimmer 嵌入 API 实战在自定义宿主中集成多个全局组件 导读 本文基于 Ember.js 仓库的 internal docs/gu前端Web框架UI组件在 Glimmer 中为嵌入式组件添加状态State、Reference 与 Tag 深入解析Ember.js Embedding 指南第三章在 Glimmer 中为嵌入式组件添加状态State、Reference 与 Tag 深入解析Ember.js Embedding 指南第三章 导读 本前端Web框架UI组件Ember.js 核心渲染引擎 Glimmer 运行时全解析组件树、Reference 与 Validator 的高效重渲染机制Ember.js 核心渲染引擎 Glimmer 运行时全解析组件树、Reference 与 Validator 的高效重渲染机制 Glimmer 是 Embe前端Web框架UI组件上一篇Neomodel语义索引完全指南向量搜索与全文检索实战下一篇WSABuilds 完整指南在 Windows 上 10 分钟装出带 Google Play 的 Android 子系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考