资讯动态

@lit/task 演进全解析:从 @lit-labs/task 毕业到 1.0.3 的关键变更与实践指南

发布时间:2026/9/13 18:46:09 来源:尧图企业网站定制
lit/task 演进全解析从 lit-labs/task 毕业到 1.0.3 的关键变更与实践指南【免费下载链接】litLit is a simple library for building fast, lightweight web components.项目地址: https://gitcode.com/GitHub_Trending/li/lit导读lit/task是 Lit 官方提供的异步任务控制器Reactive Controller专门用于解决 Lit 元素请求、处理并渲染远程数据这一高频场景例如查询 REST API 并展示结果。本文以 packages/task/CHANGELOG.md 为主线完整梳理该包从lit-labs/task实验室版本毕业为正式版lit/task的全过程并结合 task.ts 源码与 task_test.ts、deep-equals_test.ts 测试用例逐一解读每次版本变更背后的实现原理。读完本文你将掌握 Task 控制器的完整配置项、手动触发与自动运行的差异、参数相等性判定策略以及status只读化、元组类型推断等 API 演进细节能够直接上手编写健壮的异步渲染逻辑。一、版本演进总览从实验室到正式发布的里程碑lit/task的历史版本脉络记录在 packages/task/CHANGELOG.md 中当前仓库内该包版本为1.0.3见 package.json。其演进可以分为四个阶段版本类型核心内容1.0.0-pre.0Major从lit-labs/task毕业到lit/task正式位置1.0.0Major正式发布lit-labs/task变为lit/task的代理包1.0.1Patchstatus属性改为只读外部赋值将抛出TypeError1.0.2Patch改进 args 函数返回元组时的类型推断1.0.3Patch更新 README 文档毕业Graduate意味着什么1.0.0版本在 CHANGELOG 中明确标注为Major ChangesGraduate lit-labs/task to lit/task, its permanent location将 lit-labs/task 毕业到其永久位置 lit/task。毕业是 Lit 生态对实验室包的正式化流程功能与 API 已经稳定脱离实验命名空间进入正式发布轨道。CHANGELOG 同时强调lit-labs/task现在只是lit/task的代理proxy两者依赖同一个实现因此在同时依赖两者的项目中不会产生代码重复。在毕业之前的实验室版本变更历史CHANGELOG 明确指出可追溯到lit-labs/task的独立 CHANGELOG原文指向packages/labs/task/CHANGELOG.md。这也印证了 Task 控制器从实验到生产级 API 的完整发展路径。二、Task 控制器的核心机制2.1 控制器如何与宿主元素协作从 task.ts 的类定义可以看到Task是一个实现了ReactiveController接口的控制器export class Task const T extends ReadonlyArrayunknown ReadonlyArrayunknown, const R unknown, { private _host: ReactiveControllerHost; // ... }构造函数在 task.ts 中通过(this._host host).addController(this)将自身注册到宿主元素上从而在宿主每次更新时收到回调。关键的生命周期钩子如下task.tshostUpdate() { if (this.autoRun true) { this._performTask(); } } hostUpdated() { if (this.autoRun afterUpdate) { this._performTask(); } }这解释了 README 中的描述每当元素更新时参数args会被检查若发生变化则启动任务。用户需要提供两个函数task 函数TaskFunction实际执行异步操作接收args数组和{signal}选项task.tsargs 函数ArgsFunction返回一个参数数组用于判断任务是否需要重新运行task.ts。值得注意的是源码中保留了DepsFunction作为ArgsFunction的向后兼容别名task.ts注释明确标注maintained for BC with its previous name——旧名称deps依赖在早期版本中广泛使用如今统一为args。2.2 两种构造方式构造函数支持两种签名task.tsconstructor(host: ReactiveControllerHost, task: TaskConfigT, R); constructor( host: ReactiveControllerHost, task: TaskFunctionT, R, args?: ArgsFunctionT );配置对象方式第二个参数传入完整的TaskConfig包含task、args、autoRun、argsEqual、initialValue、onComplete、onError等全部选项简写方式直接传task函数与可选的args函数适合最简单的场景。实现中通过typeof task object ? task : ({task, args} as TaskConfigT, R)自动归一化两种写法task.ts。2.3 四种任务状态与渲染TaskStatus是一个as const常量对象task.ts定义了四个状态export const TaskStatus { INITIAL: 0, PENDING: 1, COMPLETE: 2, ERROR: 3, } as const;对应关系为状态数值含义INITIAL0尚未运行PENDING1正在执行中COMPLETE2成功完成结果可通过value读取ERROR3执行出错错误可通过error读取render方法task.ts根据当前状态调用渲染器对象中对应的方法initial、pending、complete(value)、error(error)这些方法通常返回一个 LitTemplateResultrenderT extends StatusRendererR(renderer: T) { switch (this._status) { case TaskStatus.INITIAL: return renderer.initial?.() as MaybeReturnTypeT[initial]; case TaskStatus.PENDING: return renderer.pending?.() as MaybeReturnTypeT[pending]; case TaskStatus.COMPLETE: return renderer.complete?.(this.value!) as MaybeReturnTypeT[complete]; case TaskStatus.ERROR: return renderer.error?.(this.error) as MaybeReturnTypeT[error]; default: throw new Error(Unexpected status: ${this._status}); } }由于任务启动与完成都会调用this._host.requestUpdate()task.ts 与 task.ts宿主元素会被强制刷新从而渲染出任务的最新状态。2.4 特殊值 initialState重置任务initialState是一个Symbol常量task.tstask 函数可以返回它来将任务状态重置为 INITIAL。在run()内部task.tsif (result initialState) { this._status TaskStatus.INITIAL; }直接跳过 COMPLETE/ERROR 分支。测试用例 task_test.ts 验证了基于state initial ? initialState : A的返回值切换逻辑。三、README 中的实战用法1.0.3 更新1.0.3版本的核心变更是Update README即文档完善。当前 README.md 给出的标准用法如下import {Task, TaskStatus} from lit/task; // ... class MyElement extends LitElement { state() private _userId: number -1; private _apiTask new Task( this, ([userId]) fetch(//example.com/api/userInfo?${userId}).then((response) response.json() ), () [this._userId] ); render() { return html divUser Info/div ${this._apiTask.render({ pending: () htmlLoading user info..., complete: (user) html${user.name}, })} !-- ... -- ; } }安装方式在项目目录内执行$ npm install lit/task3.1 配置对象写法README 中的简写形式等价于源码 JSDoc 示例中的配置对象形式task.ts两者可以互换task new Task( this, { task: async ([url, id]) { const response await fetch(${this.url}?id${this.id}); if (!response.ok) { throw new Error(response.statusText); } return response.json(); }, args: () [this.id, this.url], } );3.2 参数函数返回值约束_getArgs()内部对 args 函数返回值做了强制校验task.tsprivate _getArgs() { if (this._argsFn undefined) { return undefined; } const args this._argsFn(); if (!Array.isArray(args)) { throw new Error(The args function must return an array); } return args; }即args 函数必须返回数组否则抛出The args function must return an array错误。四、autoRun 选项自动运行与手动控制4.1 三种取值1.0 时代引入afterUpdateautoRun的类型为boolean | afterUpdatetask.ts默认值truetask.ts。源码注释task.ts详细说明了三种取值的语义取值触发时机适用场景true默认宿主更新过程中willUpdate()之后、update()之前检查参数并运行常规数据加载参数变更须在willUpdate()或更早发生宿主可在本次更新中看到任务状态变化afterUpdate宿主更新完成之后hostUpdated()检查并运行任务需要读取本次更新产生的 DOM但宿主看不到自身更新引发的任务状态变化任务必须触发第二次宿主更新才能渲染false不自动运行需显式调用run()等待用户交互如按钮点击后再触发源码注释同时给出重要提示afterUpdate模式未来很可能不兼容 SSR。当autoRun afterUpdate时为避免 change-in-update 警告run()内部通过queueMicrotask(() this._host.requestUpdate())延迟请求更新task.ts。4.2 手动运行 run()autoRun: false时任务不会随参数变化自动执行需要显式调用run()async run(args?: T) { args ?? this._getArgs(); // ... }不传参数时使用配置的 args 函数计算结果也可以传入自定义参数数组覆盖 args 函数task.ts若任务正处于 PENDING再次调用run()会先this._abortController?.abort()中止上一次运行task.ts。run()还通过自增的_callId与闭包捕获的key做最新调用校验task.ts 与 task.ts只有最近一次任务调用的结果会被写入状态较早的异步结果会被丢弃避免竞态条件下旧结果覆盖新结果。4.3 测试验证task_test.ts 覆盖了以下行为autoRun: false时任务不自动运行autoRun属性可在 Task 实例上动态赋值el.task.autoRun true/false立即生效autoRun: false时调用run()任务正常执行run()支持传入自定义参数数组测试 taskrunoptionally accepts args。五、1.0.1status 只读化的破坏性修复5.1 变更背景1.0.1的 Patch Changes 明确指出此前status是可写属性允许从外部赋值。这虽然能渲染出期望的模板却会让任务内部状态失去一致性the task was becoming internally incoherent。修复后对status赋值将抛出TypeError。5.2 实现细节源码中status只暴露了 gettertask.tsget status() { return this._status; }由于没有定义 setter在类字段属性默认可写的 JS 语义下赋值操作会命中严格模式的TypeError。同时内部的_status字段保持private私有化task.ts外部无法绕过。5.3 测试验证task_test.ts 专门新增了回归测试test(task status is not settable, async () { // ... assert.throws(() { // ts-expect-error for test el.task.status TaskStatus.ERROR; }, TypeError); });测试明确断言赋值抛TypeError并以ts-expect-error注释确认类型层面同样禁止该操作。六、1.0.2args 元组类型推断改进1.0.2的变更来自 PR #4836Improve type inference of tuples returned by the args function being used as task function parameter——即改进 args 函数返回的元组被用作 task 函数参数时的类型推断。这在Task类的泛型声明中得到了体现task.tsexport class Task const T extends ReadonlyArrayunknown ReadonlyArrayunknown, const R unknown, const类型参数修饰符让 TypeScript 将T推断为精确元组类型而非普通数组。例如() [this._userId]会被推断为readonly [number]而非readonly number[]从而使 task 函数解构([userId]) ...时获得userId: number的精确类型而非number | undefined。这一改进显著提升了以参数元组模式编写 task 函数时的类型安全性是 1.0.2 对开发体验的核心贡献。七、参数相等性判定shallowArrayEquals 与 deepArrayEqualsREADME 与1.0.2变更共同指向的一个关键设计是argsEqual选项——决定任务是否应在参数变化时自动运行。7.1 默认shallowArrayEquals默认相等函数为shallowArrayEqualstask.tsexport const shallowArrayEquals T extends ReadonlyArrayunknown( oldArgs: T, newArgs: T ) oldArgs newArgs || (oldArgs.length newArgs.length oldArgs.every((v, i) !notEqual(v, newArgs[i])));其逐元素比较使用lit/reactive-element导出的notEqual基于语义。对于字符串、数字等原始值参数这种浅比较完全够用。7.2 深度比较deepArrayEquals当参数是对象时浅比较会因引用不同而误判已变化导致任务反复运行。此时应使用deepArrayEquals它来自独立子路径模块lit/task/deep-equals.js对应源码 deep-equals.ts通过 package.json 的exports映射对外导出package.jsonimport {deepArrayEquals} from lit/task/deep-equals.js;deepArrayEquals逐元素调用deepEqualsdeep-equals.ts后者是一套完整的递归深度比较实现deep-equals.ts支持的比较类型包括类型比较策略原始值primitiveObject.is()对象构造函数相同 自有属性名集合相同 每个属性递归深度相等数组Array长度相同 逐元素递归比较Mapsize相同 每个键值对递归相等Setsize相同 每个成员都存在RegExpsource与flags相同自定义valueOf()的对象委托valueOf()比较如 Date 比较毫秒时间戳自定义toString()的对象委托toString()比较如 URL、TrustedTypes重要限制源码注释明确警告deep-equals.ts对象不能包含循环引用否则deepEquals会无限递归运行7.3 测试验证task_test.ts 展示了argsEqual: deepArrayEquals的用法args 函数每次返回新数组包装的对象deepArrayEquals判定相等从而不重复运行deep-equals_test.ts 包含大量用例测试数据借鉴自 fast-deep-equal 项目覆盖MyMap、MySet子类、BigInt 等多种边界情况。八、任务中止与 taskComplete配套能力虽然 CHANGELOG 未直接提及但abort()与taskComplete是 Task 控制器生产级使用不可或缺的配套能力且均有测试覆盖一并说明。8.1 abort()中止运行中的任务abort(reason?)仅对 PENDING 状态的任务生效task.tsabort(reason?: unknown) { if (this._status TaskStatus.PENDING) { this._abortController?.abort(reason); } }中止动作通过AbortController向 task 函数传入的signal发出。源码注释task.ts明确两点不会自动取消 task 函数本身task 函数必须主动配合要么把signal转发给fetch()等原生支持取消的 API要么监听signal的abort事件或调用signal.throwIfAborted()任务不在运行中COMPLETE / ERROR / INITIAL时调用abort()无任何效果。测试 task_test.ts 验证了abort(testing)后signal.aborted truetask_test.ts 还验证了新一次run()会获得全新的、未被中止的 signal。8.2 taskComplete等待任务完成的 PromisetaskComplete是一个 gettertask.ts返回一个在当前任务运行完成时 resolve 的PromiseR语义如下状态为 PENDING 时生成并缓存一个 in-progress 的 Promise状态为 ERROR 时返回一个以错误 reject 的 Promise其他情况INITIAL / COMPLETE返回立即 resolve 的 Promise若在旧任务运行中再次调用只要没有开启新运行会复用已缓存的 Promise每次run()开始时非 PENDING 场景会清空缓存task.ts。相关测试包括 task_test.ts新运行生成新 Promise、task_test.ts运行中复用同一 Promise、task_test.ts可 catch 错误。九、模块结构与发布形态9.1 三个导出入口从 rollup.config.js 可以看到生产构建配置了三个 entry pointdeep-equals、index、task外部依赖仅lit/reactive-element。对应源码index.tsexport * from ./task.js主入口task.ts核心控制器实现deep-equals.ts深度比较工具。9.2 依赖与版本约束package.json 显示运行时依赖为lit/reactive-element: ^1.0.0 || ^2.0.0开发依赖为lit: ^3.0.0说明该包同时兼容 reactive-element 1.x 与 2.x 两个大版本。包的exports映射为每个入口都提供了types、development开发模式构建与default三条通道符合 Lit 生态的构建规范。包采用 ESMtype: module并通过 wireit 编排build、test:devMODEdev与test:prodMODEprod等脚本测试由仓库级 web-test-runner.config.ts 驱动。十、写在最后演进背后的工程原则回顾lit/task的版本史可以提炼出三条工程原则API 稳定性优先status只读化1.0.1宁可引入TypeError这类破坏性行为也要保证控制器内部状态机的一致性——这正是一个控制器类库对状态不可被外部污染的坚持类型体验持续打磨元组推断改进1.0.2说明 Lit 团队不仅关注运行时行为也在持续优化 TypeScript 开发者的静态类型体验文档与代码同步演进1.0.3 专注于 README 更新确保autoRun、argsEqual、render等核心概念在文档层面有准确、可复现的示例参见 README.md。对于实际项目建议按以下规则选用能力参数为原始值时使用默认shallowArrayEquals无需额外配置参数含对象时引入deepArrayEquals并确保对象无循环引用需要按交互如点击按钮触发时将autoRun设为false并显式调用run()需要取消长时间请求时在 task 函数内消费options.signal并配合abort()希望任务初始即有数据时通过initialValue将任务直接置于 COMPLETE 状态并缓存初始参数task.ts。如需深入阅读源码建议从 task.ts 的TaskConfig接口注释task.ts入手再对照 task_test.ts 的 1100 余行测试理解每个配置项的边界行为。【免费下载链接】litLit is a simple library for building fast, lightweight web components.项目地址: https://gitcode.com/GitHub_Trending/li/lit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价