
lit/task 异步任务控制器完全指南从 lit-labs/task 到正式版的能力演进与实战【免费下载链接】litLit is a simple library for building fast, lightweight web components.项目地址: https://gitcode.com/GitHub_Trending/li/lit在 Lit 组件中请求、处理并渲染远程数据如查询 REST API是常见需求而Task控制器正是为此提供的一种可复用的封装模式它集成宿主元素每当元素更新时检查参数并自动发起任务。本文以本仓库 packages/labs/task/CHANGELOG.md 的版本演进为主线结合lit/task的源码、测试与配置系统讲解Task控制器从 Labs 实验包到正式lit/task的核心能力autoRun、AbortSignal取消、initialValue、可插拔参数相等函数、任务链、taskComplete等。读完本文你将掌握Task的完整 API、运行时机与状态机原理并能直接用于真实组件开发。版本与路径说明文中以当前仓库为准。lit/task正式版当前版本为 1.0.3见 packages/task/package.json源码位于 packages/task/src其前身lit-labs/task版本为 3.1.0见 packages/labs/task/package.json自 3.1.0 起仅作为lit/task的代理包。下文以lit/task为准展开。一、从 lit-labs/task 到 lit/task毕业与代理机制packages/labs/task/CHANGELOG.md 中最关键的一次变更出现在3.1.0含 3.1.0-pre.0Graduate毕业lit-labs/task正式迁移到其永久位置lit/task代理proxylit-labs/task从此只是lit/task的代理依赖两者的项目无需重复代码。这背后是仓库的双包结构正式包 packages/task 提供全部实现task.ts、deep-equals.ts而 packages/labs/task/src/task.ts 只剩下一段带deprecated注释的重新导出/** * You can import directly from lit/task now. * deprecated */ export { ArgsFunction, DepsFunction, StatusRenderer, Task, TaskConfig, TaskFunction, TaskFunctionOptions, TaskStatus, initialState, shallowArrayEquals, } from lit/task/task.js;从package.json的依赖关系也能印证lit-labs/task仅依赖lit/task: ^1.0.0见 packages/labs/task/package.json不再携带任何独立实现其exports同时暴露了.、./deep-equals.js、./task.js三个入口与正式包保持一致。迁移建议新项目直接安装lit/task存量项目可在过渡期继续使用lit-labs/task其所有 API 均来自lit/task迁移时只需替换导入来源。二、快速上手安装与最小示例安装$ npm install lit/task基础用法引自 packages/task/README.mdimport {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}, })} ; } }Task的构造签名源码 task.ts有两种形式new Task(host, taskConfig)传入配置对象new Task(host, taskFunction, args?)传入任务函数与参数函数。构造时通过this._host.addController(this)把自身注册为宿主元素的 Reactive Controller从而接入hostUpdate()/hostUpdated()生命周期。三、核心状态机TaskStatus 与运行时序3.1 四个状态TaskStatus定义在 task.tsexport const TaskStatus { INITIAL: 0, PENDING: 1, COMPLETE: 2, ERROR: 3, } as const;INITIAL初始状态任务尚未运行PENDING任务运行中COMPLETE成功完成结果存于valueERROR抛出异常错误存于error。任务的value、error、status三个 getter 分别暴露结果、错误与状态task.ts。3.2 运行时机hostUpdate 与 hostUpdatedTask通过控制器生命周期钩子决定何时检查参数并运行task.tshostUpdate() { if (this.autoRun true) { this._performTask(); } } hostUpdated() { if (this.autoRun afterUpdate) { this._performTask(); } }autoRun: true默认在宿主update()之前、willUpdate()之后检查参数。任务能看到宿主当前更新传递中对willUpdate()中产生的状态变化若参数变化则在本轮更新内触发任务并请求渲染 pending 状态。autoRun: afterUpdate在宿主更新完成DOM 已渲染之后检查参数并运行任务可以依赖宿主渲染出的 DOM但宿主本轮更新无法看到任务状态变化需要任务触发第二次更新因此宿主元素会被渲染多次。autoRun: false完全手动只能通过run()调用。触发判断逻辑在_performTask()task.ts调用参数函数取得新参数数组与上一次的参数数组用argsEqual比较不同才执行run(args)。运行时序注意任务要看到宿主当前更新中的状态变化这些变化必须在willUpdate()中完成update()/updated()中的变化要等下一轮更新才可见源码注释 task.ts。3.3 任务链不再重置 value 与 errorlit-labs/task2.0.0 的破坏性变更见 CHANGELOG任务在 pending 时不再重置value与error这使任务链成为可能——后一个任务可以安全地依赖前一个任务的value而不会被中间状态清空const a new Task( this, async ([url]) await fetch(url), () [this.url] ); const b new Task( this, async ([value]) { /* This is not thrashed */ }, () [a.value] );测试 task_test.ts 中的 task error is not reset on rerun 也验证了错误状态不会被重跑清空的行为。四、TaskConfig 完整配置项TaskConfig接口定义在 task.ts全部选项如下配置项类型默认值说明taskTaskFunctionT, R必填任务函数接收参数数组与{signal}选项argsArgsFunctionT无返回参数数组的函数每次宿主更新时调用autoRunboolean \| afterUpdatetrue是否在参数变化时自动运行afterUpdate在宿主 DOM 更新后运行argsEqual(old, new) booleanshallowArrayEquals判定新旧参数是否相等的函数initialValueR无初始值使任务直接进入 COMPLETE 状态onComplete(value) unknown无任务成功完成回调onError(error) unknown无任务失败回调任务函数类型task.tsexport interface TaskFunctionOptions { signal: AbortSignal; } export type TaskFunctionD extends ReadonlyArrayunknown, R unknown ( args: D, options: TaskFunctionOptions ) R | typeof initialState | PromiseR | typeof initialState; export type ArgsFunctionD extends ReadonlyArrayunknown () D;DepsFunction是ArgsFunction的旧名别名为向后兼容保留task.ts。五、手动控制autoRun 与 run()部分场景需要精确控制任务运行时机例如参数变了但必须等用户点击按钮才执行。autoRun: false支持在任务配置中传入也可在Task实例上直接赋值autoRun是公开可写属性见 task.ts默认值为true。run(args?)task.ts用于手动运行不传参时使用任务配置的args函数取值可传入自定义参数数组覆盖运行前会记录_previousArgs供后续自动运行比较若当前状态为 PENDING会先abort()中止上一次运行再启动新运行配合_callId递增只有最新一次调用的结果才会被采纳。测试覆盖tasks do not run whenautoRunisfalse、taskautoRunis settable、task runs whenruncalled、taskrunoptionally accepts args见 task_test.ts。六、取消任务abort() 与 AbortSignal3.0.0 引入两项与取消相关的能力CHANGELOG为任务函数提供AbortSignalPR #3996任务函数通过options.signal接收信号新增Task.abort()方法PR #3998手动中止当前任务。源码中每次运行都会创建新的AbortController并把signal传给任务函数task.tsconst key this._callId; this._abortController new AbortController(); try { result await this._task(args!, {signal: this._abortController.signal}); } catch (e) { errored true; error e; }abort(reason?)task.ts仅在中止 PENDING 状态的任务时有意义中止任务不会自动取消任务函数。任务函数必须主动处理AbortSignal要么转发给fetch()等原生支持取消的 API要么通过signal.throwIfAborted()或监听abort事件手动取消。// 转发给 fetch private _task new Task(this, { task: ([id], {signal}) fetch(/api?id${id}, {signal}).then(r r.json()), args: () [this.id], }); // 手动取消 private _task new Task(this, { task: async ([id], {signal}) { const data await load(id); signal.throwIfAborted(); // 若已中止则抛错 return data; }, args: () [this.id], });相关测试task functions receive an AbortSignal、tasks can be abortedtask_test.ts。七、初始值与回调initialValue、onComplete、onErrorinitialValue3.0.0 新增提供初始值时任务在构造阶段直接进入COMPLETE状态并存储该值同时记录当前参数此后仅当参数变化时才再次自动运行源码 task.ts。文档注释强调初始参数应与初始值保持一致即假设初始参数产生初始值。测试 tasks can have initialValuetask_test.ts。onComplete/onError2.0.0 新增任务完成/失败时回调返回值会被忽略源码中回调抛错会被捕获忽略见 task.ts。对应测试 onComplete callback is called、onError callback is calledtask_test.ts。八、参数相等策略shallowArrayEquals 与 deepArrayEqualsargsEqual决定参数变化是否触发自动运行。默认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其本身基于。参数为字符串等原始值时完全够用参数为对象时则需更精细的比较。为此提供了deepArrayEqualsdeep-equals.ts按参数逐个用deepEquals比较可处理原始值用Object.is因此NaN相等、0与-0不等普通对象要求构造器相同、自有属性名集合相同、每个属性深度相等数组、Map、Set、RegExp比较 source 与 flags实现了自定义valueOf()的对象如Date实现了自定义toString()的对象如URL、TrustedTypes使用方式import {Task, deepArrayEquals} from lit/task; // 或 import {deepArrayEquals} from lit/task/deep-equals.js; new Task(this, { task: async ([filter]) query(filter), args: () [this.filter], // filter 是对象 argsEqual: deepArrayEquals, });deepEquals的测试用例移植自fast-deep-equals见 deep-equals_test.ts覆盖标量、对象、数组、Date、RegExp、函数、Map、Set、TypedArray、bigint 等十余组场景。注意其注释的边界对象不能包含循环引用否则会无限递归。九、渲染任务状态render() 与 render 返回类型推断render()task.ts接收一个StatusRenderer对象可选实现initial、pending、complete(value)、error(error)四个方法按当前状态分发调用通常返回TemplateResultexport type StatusRendererR { initial?: () unknown; pending?: () unknown; complete?: (value: R) unknown; error?: (error: unknown) unknown; };render() { return this.task.render({ initial: () html准备加载…, pending: () htmlLoading..., complete: (data) htmlul${data.map(d htmlli${d}/li)}/ul, error: (e) html加载失败${e.message}, }); }版本演进中的类型改进3.0.0 的 Infer the return type of Task.render()PR #4008与 3.0.1 的 Fix Task.render()s return type always being undefinedPR #4070完善了 render 返回类型推断1.1.3 起参数数组类型改为 readonly 以配合as const推断PR #3131。仓库中有专门的 type-only render return type test 与 tuple type arguments are inferred 类型测试task_test.ts。1.0.2 还改进了 args 函数返回元组作为任务函数参数时的类型推断。十、等待完成taskComplete PromisetaskCompletetask.ts返回一个 Promise在当前任务运行完成时 resolve。关键语义不主动抛错2.1.2 的 PR #3947除非用户显式请求taskComplete否则任务本身不会抛出未捕获异常错误状态后拒绝若任务出错taskComplete返回 rejected promise2.1.2 的 PR #3953 修复了错误状态下taskComplete不 reject 的问题多次运行共享新一轮任务运行期间复用同一个 promise只在关键节点重新生成测试 generates a new taskComplete promise on run vs initial、reuses taskComplete promise in the middle of runs 等见 task_test.ts。典型用法在事件处理或测试中await this.task.taskComplete等待异步结果。十一、initialState重置任务状态initialState是特殊哨兵值Symbol见 task.ts。任务函数返回initialState时任务状态被重置为INITIAL源码 task.ts。这一机制自 1.0.0-pre.2 引入测试 task functions can return initial statetask_test.ts验证了该行为。十二、其他重要演进与维护细节变更警告修复2.1.0 通过延迟初始宿主更新修复了任务的 change-in-update 警告PR #36603.0.0 将任务默认执行时机从hostUpdated()提前到hostUpdate()PR #4004。测试 no change-in-update warning、Elements only render once for pending tasks、Tasks can depend on host rendered DOMtask_test.ts覆盖这些时序保证。类型修复3.0.2 修复了TypeFunction类型在提供使用 abort signal 的任务函数时出现的类型错误PR #41611.0.1 将status变为只读属性外部赋值会抛出TypeError见 packages/task/CHANGELOG.md1.1.3 新增 package exports 的types入口。无参任务1.1.0 起无参数的任务默认也会运行PR #2336此前需要显式配置。测试 task without args do not run、tasks with empty args array run oncetask_test.ts覆盖了无参数与空参数数组两种边界。破坏性变更汇总3.0.0 起移除performTask()与shouldRun()两个受保护方法改为可插拔argsEqual机制。十三、测试与构建Task的行为有完善的浏览器测试保障测试文件位于 task_test.ts覆盖自动运行、参数比较、取消、状态渲染、回调、taskComplete 等与 deep-equals_test.ts。构建与测试脚本定义在 packages/task/package.json 中通过 wireit 编排test:dev/test:prod分别以开发与生产模式在 web-test-runner 中运行测试。结语Task控制器的演进脉络清晰可见从lit-labs/task的实验探索自动运行、任务链、回调到 3.0.0 补齐AbortSignal取消与initialValue再到毕业为lit/task1.0.0 并持续修复类型与运行时细节。当前 packages/task/src/task.ts 是唯一实现来源packages/labs/task 仅作代理。在真实项目中使用时直接安装lit/task并依据autoRun三种取值、argsEqual策略与 abort 语义即可写出可取消、可复用、状态清晰的异步数据渲染逻辑。【免费下载链接】litLit is a simple library for building fast, lightweight web components.项目地址: https://gitcode.com/GitHub_Trending/li/lit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考