2.5 指令与管道


文档摘要

2.5 指令与管道:变更呈现的转换器 指令在模板里改变元素的结构或外观(结构型如 ngIf/ngFor,属性型如 ngClass/ngStyle);管道在插值中转换显示值。两者与变更检测的关系最直接:结构型指令增删视图节点、改变检查的对象集合;管道分纯与不纯,纯管道只在输入引用变化时重算,不纯管道每轮检查都算——一字之差,性能天壤之别。 上一节定义了"绑定是采样探针",本节讲两类"转换器":指令转换视图结构,管道转换显示值。 本节要拿下什么 2.4 解决了"数据怎么进模板",本节要拿下的是数据进门之后还能被谁改写、以什么代价改写: 手写一个属性型指令与一个结构型指令,说明星号语法与模板标签的等价关系。 解释 ngFor 中 trackBy 的作用机制:复用 DOM 与组件实例的条件。

2.5 指令与管道:变更呈现的转换器

指令在模板里改变元素的结构或外观(结构型如 ngIf/ngFor,属性型如 ngClass/ngStyle);管道在插值中转换显示值。两者与变更检测的关系最直接:结构型指令增删视图节点、改变检查的对象集合;管道分纯与不纯,纯管道只在输入引用变化时重算,不纯管道每轮检查都算——一字之差,性能天壤之别。

上一节定义了"绑定是采样探针",本节讲两类"转换器":指令转换视图结构,管道转换显示值。

本节要拿下什么

2.4 解决了"数据怎么进模板",本节要拿下的是数据进门之后还能被谁改写、以什么代价改写:

  1. 手写一个属性型指令与一个结构型指令,说明星号语法与模板标签的等价关系。
  2. 解释 ngFor 中 trackBy 的作用机制:复用 DOM 与组件实例的条件。
  3. 区分纯管道与不纯管道的求值策略,写出各自的适用场景。
  4. 手写一个自定义纯管道并测算其相对方法调用的收益。

一、属性型指令:改外观不改结构

import { Directive, ElementRef, Input, HostListener, Renderer2, inject } from '@angular/core'; // 高亮指令:宿主元素获得条件高亮,鼠标悬停时加深 @Directive({ selector: '[appHighlight]' }) export class HighlightDirective { @Input('appHighlight') set color(c: string | null) { this.paint(c || 'yellow'); // 输入一变(检查期写入)立即重涂 } private el = inject(ElementRef); private renderer = inject(Renderer2); @HostListener('mouseenter') onMouseEnter() { this.paint('orange'); } @HostListener('mouseleave') onMouseLeave() { this.paint(null); } private paint(color: string | null) { // Renderer2 而非直接操作原生节点:SSR 与安全两重原因(第 3 章呼应) this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', color); } } // 用法:<p [appHighlight]="'#ffe'">待办事项</p>

属性型指令不增删节点,只改宿主的属性、样式、事件——检查时它的输入被当作普通绑定对待。

二、结构型指令与星号语法

<!-- 星号写法 --> <li *ngFor="let o of orders; trackBy: trackByOrderNo">{{ o.orderNo }}</li> <!-- 等价展开:星号是语法糖,真实结构是包了一层模板 --> <ng-template ngFor let-o [ngForOf]="orders" [ngForTrackBy]="trackByOrderNo"> <li>{{ o.orderNo }}</li> </ng-template>

展开式揭示了机制:结构型指令拿到的是模板,数据变化时由指令自己决定实例化或销毁视图。trackBy 是本节最重要的性能知识点——ngFor 默认按对象引用识别条目,数据刷新后引用全变,整列表 DOM 推倒重建;提供 trackBy 让框架按业务键识别,键相同的条目复用原有视图与组件实例,只更新绑定

trackByOrderNo(_i: number, o: { orderNo: string }) { return o.orderNo; // 单号是稳定业务键 }

⚠️ 坑:trackBy 返回值必须是跨刷新稳定的标识,返回数组下标等于没写;返回随机数则每次全部重建。

三、管道:纯与不纯的分水岭

管道用于插值中的显示转换。内置管道如 date、number、async;自定义示例如下:

import { Pipe, PipeTransform } from '@angular/core'; // 纯管道(默认):Angular 认为输入是"纯"的——只有引用变化或基础类型值变化才重算 @Pipe({ name: 'rmb' }) export class RmbPipe implements PipeTransform { transform(value: number | null, withSymbol = true): string { if (value == null) return '--'; const yuan = (value / 100).toFixed(2); return withSymbol ? `¥${yuan}` : yuan; } } // 用法:{{ order.amount | rmb }} 或 {{ order.amount | rmb:false }}

纯管道的收益机制:Angular 对纯管道会缓存上次输入引用,引用没变就直接复用上次结果,变换函数根本不执行。对比上一节"模板里调用 sortByAmount 每轮都算"的禁令——把变换搬进纯管道,就变成"引用变了才算"。

不纯管道则是每轮检查都执行:

// 不纯管道:每轮变更检测都调用 transform @Pipe({ name: 'timeAgo', pure: false }) export class TimeAgoPipe implements PipeTransform { transform(ts: number): string { const diff = Math.floor((Date.now() - ts) / 1000); return diff < 60 ? `${diff} 秒前` : `${Math.floor(diff / 60)} 分钟前`; } }

timeAgo 必须不纯:输出依赖"当前时间"这个框架感知不到的输入。但每个不纯管道都是检查预算的常驻消费者,一个页面上百条不纯 timeAgo 是真实的性能事故来源。替代方案:定时器每 30 秒改一次服务字段,把"时间流动"变成框架可感知的数据变化。

图:纯与不纯管道的求值路径对比

图:纯与不纯管道的求值路径对比

四、案例:状态徽标管道的完整过程

背景:订单状态在后端是数字码(0/1/2/3),界面要显示中文徽标并配色。最初团队在组件里写了个方法 badgeOf(status) 直接在插值里调用,列表页 500 行时明显卡顿。
操作:改造为纯管道:

@Pipe({ name: 'statusBadge' }) export class StatusBadgePipe implements PipeTransform { transform(code: number): { text: string; cls: string } { const map = [ { text: '待支付', cls: 'b-warn' }, { text: '已付款', cls: 'b-info' }, { text: '已发货', cls: 'b-ok' }, { text: '已完成', cls: 'b-done' } ]; return map[code] ?? { text: '未知', cls: 'b-mute' }; } } // 模板:<span class="badge {{ o.status | statusBadge }}...">{{ (o.status | statusBadge).text }}</span>

结果:每轮检查中 500 个插值只在各自 status 值变化时才重算,卡顿消失。
解读:基础类型输入的"纯"判定按值比较,数字没变就复用结果——纯管道把"每轮算 500 次"压缩到"实际变化才算"。
变式:若徽标还要叠加"超时变红"(依赖当前时间),纯管道不够用,正确做法是把超时判定做成数据:定时任务给超时订单打标记字段,管道仍是纯的。

本节要点回顾

  • 属性型指令改宿主外观,输入按普通绑定被检查;优先 Renderer2 而非原生操作。
  • 结构型指令持模板增删视图,星号是 ng-template 包装的语法糖。
  • trackBy:按稳定业务键复用视图与实例,是列表性能第一杠杆。
  • 纯管道缓存:引用/值不变则零执行;不纯管道每轮必算,慎用且限量。
  • 主线上的一环:指令决定"检查哪些节点",管道决定"表达式算不算"——两者都在改写检查的成本结构。

下一节进入入口类构件:路由与导航。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U