2.4 模板与数据绑定


文档摘要

2.4 模板与数据绑定 数据绑定是把组件类的数据接到模板、把模板事件接回类 的语法体系:插值、属性绑定、事件绑定与双向绑定。站在变更检测主线上,绑定表达式就是被采样的探针——每轮检查重算一次,新旧值不同才写 DOM。理解绑定的求值时机,就理解了 Angular 性能话题的一半。 上节明确了"模板重新读取字段"是更新的第三段,本节把模板本身讲透:四种绑定的语法、求值方向,以及模板表达式写法的性能红线。 学习目标 正确使用四种绑定语法并说明各自的数据流向。 解释属性绑定与插值在目标为 DOM 属性时的等价关系。 写出安全的双向绑定,说明香蕉语法糖的展开形式。 列出模板表达式的三条禁令(调用重方法、赋值、new),并说明原因。 一、四种绑定一张图 属性绑定与插值的等价性值得点破: 本质是 的简写。

2.4 模板与数据绑定

数据绑定是把组件类的数据接到模板、把模板事件接回类 的语法体系:插值、属性绑定、事件绑定与双向绑定。站在变更检测主线上,绑定表达式就是被采样的探针——每轮检查重算一次,新旧值不同才写 DOM。理解绑定的求值时机,就理解了 Angular 性能话题的一半。

上节明确了"模板重新读取字段"是更新的第三段,本节把模板本身讲透:四种绑定的语法、求值方向,以及模板表达式写法的性能红线。

学习目标

  1. 正确使用四种绑定语法并说明各自的数据流向。
  2. 解释属性绑定与插值在目标为 DOM 属性时的等价关系。
  3. 写出安全的双向绑定,说明香蕉语法糖的展开形式。
  4. 列出模板表达式的三条禁令(调用重方法、赋值、new),并说明原因。

一、四种绑定一张图

<!-- 组件模板:订单搜索框 --> <div class="search"> <!-- 1 插值:单向出,类 → 文本 --> <h3>订单检索(共 {{ total }} 条)</h3> <!-- 2 属性绑定:单向出,类 → 元素属性/组件输入/指令输入 --> <input [value]="keyword" [placeholder]="hint" [disabled]="querying"> <!-- 3 事件绑定:单向进,模板 → 类方法 --> <button (click)="search()" [disabled]="querying">查询</button> <!-- 4 双向绑定:进出都有,语法糖等价于 [value]="keyword" + (valueChange)="keyword = $event" --> <input [(ngModel)]="keyword" name="kw"> </div>

属性绑定与插值的等价性值得点破:{{ keyword }} 本质是 [textContent]="keyword" 的简写。也就是说,插值没有特殊通道,它就是一次属性绑定,检查时同样走"求值 → 比对 → 写入"流程。

香蕉语法 [(ngModel)] 的展开形式务必亲手写一遍,双向绑定失灵时全靠它排错——十有八九是输出事件名对不上(框架约定输入属性名加 Change 后缀构成输出名)。

二、求值方向与检查成本

绑定表达式只在变更检测运行时求值,但"每轮都求值"意味着成本与绑定数量成正比。看一组对照:

@Component({ selector: 'app-cost-demo', template: ` <!-- 差:每轮检查都调用排序,列表大时每轮白付一次排序成本 --> <li *ngFor="let o of sortByAmount(orders)">{{ o.orderNo }}</li> <!-- 好:数据变化时在类里排好,检查只读字段 --> <li *ngFor="let o of sortedOrders">{{ o.orderNo }}</li> <!-- 差:每轮 new 一个新数组/新对象,引用永不相等,浪费比对 --> <app-order-list [orders]="[...orders]"></app-order-list> <!-- 好:传既有引用,引用不变则子组件输入判定为"没变" --> <app-order-list [orders]="orders"></app-order-list> ` }) export class CostDemoComponent { orders = [ { orderNo: 'A-1', amount: 199 }, { orderNo: 'A-2', amount: 88 } ]; sortedOrders = [...this.orders].sort((a, b) => b.amount - a.amount); sortByAmount(list: { orderNo: string; amount: number }[]) { return [...list].sort((a, b) => b.amount - a.amount); } }

由此得出模板表达式三条禁令:不调用有副作用或重计算的方法;不做赋值;不创建新对象字面量当输入传。禁令的共同根源就一条——表达式每轮检查都会重算,你写的不是"执行一次"的代码,是"每轮执行一次"的代码

💡 直觉:把模板表达式当成数据库视图——必须无副作用、可重复求值、足够廉价。

三、绑定语法全景表

语法 方向 目标 求值时机
{{ expr }} 类→视图 文本内容 每轮检查
[prop]="expr" 类→视图 元素属性、组件/指令输入 每轮检查
(event)="stmt" 视图→类 DOM 事件、组件输出 事件发生时
[(x)]="field" 双向 输入+同名输出 两者各按各的时机
#ref / @ViewChild 视图→类 元素/组件引用 视图初始化后

图:一轮检查中各类绑定的求值顺序

图:一轮检查中各类绑定的求值顺序

四、案例:搜索框防抖的完整过程

背景:运营后台订单搜索,要求输入停顿 300 毫秒再触发查询,避免每敲一键打一次接口。
操作:表单控件的 valueChanges 流上做防抖(RxJS 细节在 2.10 节),结果写入组件字段:

import { Component } from '@angular/core'; import { FormControl } from '@angular/forms'; import { debounceTime, filter } from 'rxjs/operators'; @Component({ selector: 'app-order-search', template: ` <input [formControl]="kw" placeholder="输入单号或关键词"> <p *ngIf="lastQuery">最近查询:{{ lastQuery }}({{ resultCount }} 条结果)</p> ` }) export class OrderSearchComponent { kw = new FormControl(''); lastQuery = ''; resultCount = 0; constructor() { this.kw.valueChanges.pipe( debounceTime(300), // 停顿 300ms 才放行 filter(v => (v ?? '').length >= 2) // 至少两个字符 ).subscribe(v => { this.lastQuery = v ?? ''; this.resultCount = 42; // 实际场景换成 HTTP 调用 // 订阅回调属于异步事件,结束后一轮检查自然到来 }); } }

结果:连续快速输入时界面不闪、请求不发;停顿后一次更新。
解读:防抖把高频数据变更合并成低频,检查轮数随之下降——这是"减少被检查次数"思路的第一次亮相,第 3 章性能一节会把它系统化。
变式:把 debounceTime 换成 throttleTime(节流,固定间隔放行一次),适合滚动加载类"宁多勿漏"的场景;两种合并策略的选择标准是用户对中间态的容忍度。

本节要点回顾

  • 四种绑定:插值即属性绑定简写;双向绑定是输入+输出的语法糖,展开式是排错钥匙。
  • 求值模型:展示类绑定每轮检查重算,事件绑定只在事件时执行——成本归属完全不同。
  • 三条禁令:模板表达式不做重计算、不赋值、不造新引用,根源是"每轮重算"。
  • 检查顺序:自上而下、父到子,叶子组件输入最后写入。
  • 主线上的一环:绑定是采样探针,下一节讲改变呈现形态的指令与管道。

下一节把模板的表达力扩展出去:指令与管道。


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