ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Angular 中基于 Signal 的异步数据管理:使用 Resource API 构建响应式用户资料加载器

Angular 中基于 Signal 的异步数据管理:使用 Resource API 构建响应式用户资料加载器 Angular 中基于 Signal 的异步数据管理使用 Resource API 构建响应式用户资料加载器【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular在 Angular 中异步数据如 HTTP 请求返回的结果的状态管理一直是开发中的高频场景数据是否加载中、是否出错、如何随参数变化自动重新请求、请求如何被取消……针对这些问题Angular 的 Resource API 给出了一个完全以 Signal 为中心的声明式答案。本文以官方交互式教程《Managing async data with signals using the Resources API》为核心在 Angular 仓库的实战环境中通过一个用户资料加载器示例逐步讲解resource()函数的用法并结合 resource 源码 与 Resource API 类型定义 剖析其底层工作方式。学完本文你将能独立使用resource()加载异步数据、驱动参数变化自动重载、处理加载/错误/成功三种界面状态并理解status()、value()、error()、hasValue()与reload()背后的设计原理。本教程的完整代码以练习与答案两种形态存放在仓库中练习起点见 src/app/app.ts完整实现见 answer/src/app/app.ts配套的模拟 API 位于 src/app/user-api.ts。为什么需要 Resource API在 Angular Signal 生态中signal()用于存放同步状态computed()用于派生只读状态linkedSignal()用于描述跟随另一个信号变化、但可被局部覆写的状态。然而真正的业务数据往往来自远端是一次异步的、会失败、需要取消、且需要随参数变化重新发起的操作。Resource API 正是为此设计它把一次异步读取操作抽象为一个响应式资源让数据加载过程拥有内建的加载状态、错误处理和请求管理能力。在引入 Resource 之前开发者通常需要手写一组signaleffect 手动 cleanup 的样板代码引入后这份样板被封装进resource()工厂函数与底层的ResourceImpl类中。值得强调的是源码注释明确了它的适用边界resource是为读操作read operations设计的而非变更mutation操作——因为资源被销毁或请求对象变化时会通过AbortSignal取消进行中的加载这可能会过早中断一次变更见 resource.ts。在实践中这意味资源加载适合承载 GET 类请求写操作应使用传统的事件处理器或 RxJS 流程。前提mock API 与信号生态在动手前先理解教程使用的模拟数据源 src/app/user-api.ts// Mock API function for loading user data export async function getUserData(id: number): Promise{name: string; email: string} { // Simulate network delay await new Promise((resolve) setTimeout(resolve, 1000)); // Simulate potential errors if (id 999) { throw new Error(User not found); } return { name: User ${id}, email: user${id}example.com, }; }这个函数刻意模拟了两个真实网络场景1000ms 的网络延迟以及用户不存在id 为 999时抛出的异常。它返回一个Promise{name, email}正是后续resource()的loader需要承载的载荷形态。在整个教程中它充当远程 API 的角色而 Resource 负责将它的进行中 / 成功 / 失败过程映射为可被模板读取的信号状态。Step 1导入resource与 mock API一切从导入开始。需要把resource加入既有的angular/core导入列表同时引入模拟 API 函数// Add resource to existing imports import {Component, signal, computed, resource, ChangeDetectionStrategy} from angular/core; // Import mock API function import {getUserData} from ./user-api;组件同时使用了signal承载 userId、computed派生加载/错误状态与resource承载异步数据这清晰体现了 Resource API 与 Signal 体系的无缝衔接——它不取代 Signal而是构建在 Signal 之上。Step 2创建加载用户数据的 resource在组件类中新增一个属性基于一个用户 ID 信号创建资源。当userId信号变化时资源会自动携带新参数重新执行加载userId signal(1); userResource resource({ params: () ({id: this.userId()}), loader: (params) getUserData(params.params.id), });逐项拆解resource()的入参对象params也支持旧名称request一个响应式函数返回本次请求的描述对象这里包裹了userId。它的返回值参与响应式依赖追踪每当读取过的信号发生变化Resource 就认为请求内容变了自动触发新一轮加载。如果完全不提供paramsloader 将不会自动重跑除非显式调用reload()详见 api.ts 中的 BaseResourceOptions。loader接收ResourceLoaderParams其中包含params即上面params函数的返回值、abortSignal与previous.status见 ResourceLoaderParams。loader 返回Promise时即 Promise 型资源返回信号或信号的 Promise时即流式streaming资源二者不可同时指定见 PromiseResourceOptions / StreamingResourceOptions。从源码层面看resource()内部首先做注入上下文校验然后把params与loader组装进new ResourceImpl(...)见 resource.ts。ResourceImpl的状态机由两个linkedSignal驱动一个外部请求信号extRequest把 params 求值结果与 reload 计数器绑定一个内部状态信号state两者协作实现参数一变即瞬时切换状态、再异步推进到结果的行为见 resource.ts。这正是教程反复强调的Resources are reactive的底层来源。Step 3与资源交互——改参数与手动重载加载器并非只能被动响应参数变化它同样接受指令式操作loadUser(id: number) { this.userId.set(id); } reloadUser() { this.userResource.reload(); }这里呈现了两种触发重载的途径二者对应完全不同的底层语义改参数触发loadUser()通过userId.set(id)修改信号进而让params重新求值。参数变化时extRequest会携带一个新请求对象statelinked signal 看到请求已变化就判定这属于新请求进入loading首载状态并执行 loader。手动重载reloadUser()调用userResource.reload()。查看 reload() 实现 可以发现它不做请求体变化而是把请求对象里的reload计数器加一使资源进入reloading状态——此时value()仍会保留上一次已取回的数据只有状态信号变为reloading。同时reload()在资源处于idle或loading时返回false不重启进行中的加载成功发起时才返回true这是调用方可以判别的返回值契约。选择哪种方式取决于语义参数本质不同如翻页、切换用户 ID应当驱动params而刷新同一份数据应当走reload()。教程中的界面把两种方式都暴露给了用户Load User 1/2按钮演示前者Reload按钮演示后者。Step 4用 computed 信号派生资源状态模板需要根据加载状态渲染不同内容因此用computed把资源状态折叠成布尔开关isLoading computed(() this.userResource.status() loading); hasError computed(() this.userResource.status() error);status()是资源暴露的核心信号。值得留意的是教程正文用 loading、success、error 三个词帮助学生建立直观概念但当前仓库中ResourceStatus的完整取值比这更精细。依据 api.ts 的类型注释实际状态共六种状态含义value() 的行为idle没有有效请求不会执行加载返回undefinedloading因响应式依赖变化而加载新值返回undefinedreloading针对同一请求重新拉取新值继续返回上一次取回的值resolved加载完成持有 loader 返回的值返回加载结果error加载抛出错误返回undefined错误存于error()local值被.set()/.update()就地覆写返回本地设置的值可以看到教程代码里的两个判断各司其职status() loading只覆盖首载过程刷新期间显示旧值不闪烁Loading文案status() error则精确命中出错场景。资源还提供一系列开箱即用的成员见 Resource 接口value()只读信号当前已加载的数据出错状态下读取它会抛出错误。status()上面列举的六态状态信号。error()出错时返回最后一次错误。isLoading信号形式的快捷标志。查看 BaseWritableResource 构造 可发现它由status() loading || status() reloading计算而来覆盖面比单独比对loading更广——教程为了概念拆解使用了computed自建标志而库本身已内置isLoading。hasValue()响应式函数安全地判断是否存在有效数据实现上会先检查错误态出错时返回false再判断value()是否为undefined见 resource.ts。snapshot()把status与value/error打包成不可变快照便于整体传递。Step 5连接按钮并把状态渲染进模板模板骨架由教程预先给出最终答案见 answer/src/app/app.ts把交互与展示完整接好。Part 1为按钮绑定点击处理器button (click)loadUser(1)Load User 1/button button (click)loadUser(2)Load User 2/button button (click)loadUser(999)Load Invalid User/button button (click)reloadUser()Reload/button前三个按钮向loadUser传入不同 id——尤其是 999它会在user-api中触发throw new Error(User not found)用于验证错误分支Reload按钮则演示对同一用户的强制刷新。Part 2用 if 处理加载、错误、成功三分支if (isLoading()) { pLoading user.../p } else if (hasError()) { p classerrorError: {{ userResource.error()?.message }}/p } else if (userResource.hasValue()) { div classuser-info h3{{ userResource.value().name }}/h3 p{{ userResource.value().email }}/p /div }这是一段典型的资源状态渲染结构值得注意的工程细节分支顺序很重要先判断 isLoading首载遮罩再判断错误最后才信任hasValue()取数据避免在错误态去读取value()那会抛出异常参见前文对value语义的说明。userResource.error()?.message使用了可选链因为error()在未出错时是undefined且 loader 抛出的非 Error 值会被源码中的encapsulateResourceError包装成Error实例保证.message始终可读见 resource.ts。hasValue()作为最后的守卫确保仅在有值可用时才渲染数据区域。教程把资源的状态检查方式汇总如下它们是模板与资源交互的完整工具箱isLoading()—— 拉取数据期间为真hasError()—— 发生错误时为真userResource.hasValue()—— 有可用数据时为真userResource.value()—— 访问已加载数据userResource.error()—— 访问错误信息。配套样式文件 src/app/app.css 用.error的红色#d32f2f与.user-info的绿色#2e7d32区分错误与成功状态的视觉反馈button与.status区给出了常规的间距与边框样式确保示例开箱即用即可观察。状态机如何跑起来一次加载的源码级旅程要真正理解 Resource值得把点击 Load User 2到页面渲染出 User 2之间发生的事在源码层面串一遍。核心实现全部位于 packages/core/src/resource/resource.tsloadUser(2)执行userId.set(2)params函数里对userId()的读取使其失效。extRequestlinked signal重算携带新的请求对象statelinked signal 检测到请求变化把状态置为loadingresource.ts。statuscomputed 将内部状态投影为公开六态此时模板中的isLoading()变真渲染Loading user...。内部effectloadEffect被触发它会先abortInProgressLoad()取消上一次未完成的请求pendingController.abort()resource.ts并注册一个PendingTasks任务用于阻塞应用稳定性判定然后在 untracked 上下文中调用 loaderresource.ts。loader 只依赖params侧的重活性返回值则刻意不参与信号追踪避免跨await的追踪歧义。loader 返回的 Promise 完成后进入是否丢弃本次结果的裁决若abortSignal.aborted或当前请求已不是发起时那个extRequest已被再次更新则静默丢弃旧响应不污染新状态resource.ts——这正是教程所说自动取消与清理的落地机制。状态写入resolved或失败则写入错误流状态投影为errorvalue 信号更新模板渲染数据区最后 resolve 掉 PendingTasks 注册的任务应用据此恢复稳定。当组件被销毁时DestroyRef回调调用destroy()它会销毁 effect、中止进行中的请求并把状态归位为idleresource.ts杜绝了传统异步组件常见的销毁后仍更新状态的内存泄漏与异常。值得注意的一点整个加载循环中取消是针对最近一次请求的。任何新请求出现都会立刻中止旧请求——也就是说当用户快速依次点击 Load User 1、Load User 2 时前一次请求即使慢也会被放弃界面上永远只呈现最后一次操作的结果这正是reload()会返回false、避免重复请求这一设计要守护的一致性。掌握四组核心概念教程收尾给出了四组必须记住的关键点结合本文的源码分析它们可以映射为具体实现依据Resources are reactive资源是响应式的params读取的信号一变化linked signal 驱动自动重载手动场景则通过reload()的计数器机制刷新同一请求。Built-in state management内建状态管理status()、value()、error()三个信号覆盖六态状态机idle/loading/reloading/resolved/error/local外加isLoading、hasValue()、snapshot()等便捷成员。Automatic cleanup自动清理AbortController取消过期请求、DestroyRef在销毁时清理资源、过期响应的丢弃裁决保证不写入脏数据。Manual control手动控制reload()触发刷新、.set()/.update()写入本地值并进入local态、destroy()手动销毁资源并取消进行中的请求。这套能力并非教学玩具——ResourceStatus、Resource、WritableResource、ResourceRef等类型均标注为publicApi 22.0的公开 API见 api.tsResourceImpl还支持通过options.idTransferState在服务端渲染时缓存并在客户端复用数据见 resource.ts因此可直接用于真实业务。将 官方 Signals 教程目录 中的这个示例跑通也就掌握了把任意 Promise/流式数据源接入 Angular 响应式渲染管线的基本范式。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表