- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
本篇文章基于 NG-ZORRO(ng-zorro-antd)开源仓库中components/tree/demo/basic-controlled.ts与basic-controlled.md这一官方受控示例,系统讲解nz-tree组件在受控模式下的核心用法:如何通过nzCheckedKeys、nzExpandedKeys、nzSelectedKeys三个输入属性初始化勾选、展开、选中状态,如何监听nzClick、nzExpandChange、nzCheckboxChange事件,以及受控状态下数据同步与底层实现的原理。读者学完本文后,可以独立写出可复制的树形结构受控场景(如权限勾选、目录展开记忆、列表选中回显),并能结合源码理解各状态之间的联动关系。
一、示例概览:一个"受控"的树长什么样
官方示例位于 components/tree/demo/basic-controlled.ts,对应说明文档 components/tree/demo/basic-controlled.md(zh-CN 标题为"受控操作示例",en-US 为 "basic controlled example",默认示例)。"受控"的含义是:树的关键状态(勾选、展开、选中)不再由组件内部自己记忆,而是由外部通过输入属性传入、通过输出事件回传,从而让开发者在父组件中拥有状态的绝对主导权。
示例组件的模板如下(源码):
<nz-tree [nzData]="nodes" nzCheckable nzMultiple [nzCheckedKeys]="defaultCheckedKeys" [nzExpandedKeys]="defaultExpandedKeys" [nzSelectedKeys]="defaultSelectedKeys" (nzClick)="nzEvent($event)" (nzExpandChange)="nzEvent($event)" (nzCheckboxChange)="nzEvent($event)" />对应的组件类:
import { Component } from '@angular/core'; import { NzFormatEmitEvent, NzTreeModule } from 'ng-zorro-antd/tree'; @Component({ selector: 'nz-demo-tree-basic-controlled', imports: [NzTreeModule], template: `/* 见上方模板 */` }) export class NzDemoTreeBasicControlledComponent { defaultCheckedKeys = ['0-0-0']; defaultSelectedKeys = ['0-0-0']; defaultExpandedKeys = ['0-0', '0-0-0', '0-0-1']; readonly nodes = [ /* 三层树形数据,见下文 */ ]; nzEvent(event: NzFormatEmitEvent): void { console.log(event); } }关键点提炼:
nzCheckable开启节点前的复选框;nzMultiple允许同时选中多个节点;defaultCheckedKeys默认勾选'0-0-0';defaultSelectedKeys默认选中'0-0-0';defaultExpandedKeys默认展开'0-0'、'0-0-0'、'0-0-1'三个非叶子节点;- 三个输出事件统一交给
nzEvent在控制台打印NzFormatEmitEvent结构,便于观察每次交互产生的完整数据。
二、受控三件套:checked / expanded / selected 的状态注入
受控示例的核心是这三个字符串数组输入属性,它们的完整定义与默认值如下(来自组件 API 文档 components/tree/doc/index.en-US.md):
| 属性 | 作用 | 类型 | 默认值 |
|---|---|---|---|
[nzCheckedKeys] | 指定默认勾选的节点 key 列表 | string[] | [] |
[nzExpandedKeys] | 指定默认展开的节点 key 列表 | string[] | [] |
[nzSelectedKeys] | 指定默认选中的节点 key 列表 | string[] | [] |
注意示例中三者的取值:defaultExpandedKeys只包含'0-0'、'0-0-0'、'0-0-1'三个非叶子节点,叶节点(如'0-0-0-0'、'0-0-2')不在其中——这是合理设计,因为叶子节点没有可展开的子层级,无需也不应出现在展开列表里(测试用例也验证了这一点,见下文"源码验证")。
树数据nzData的结构
受控状态都通过 key 引用节点,因此nzData中的key必须全局唯一。示例数据为三层结构:
readonly nodes = [ { title: '0-0', key: '0-0', expanded: true, // NzTreeNodeOptions 也支持在数据里直接声明状态 children: [ { title: '0-0-0', key: '0-0-0', children: [ { title: '0-0-0-0', key: '0-0-0-0', isLeaf: true }, { title: '0-0-0-1', key: '0-0-0-1', isLeaf: true }, { title: '0-0-0-2', key: '0-0-0-2', isLeaf: true } ] }, { title: '0-0-1', key: '0-0-1', children: [ { title: '0-0-1-0', key: '0-0-1-0', isLeaf: true }, { title: '0-0-1-1', key: '0-0-1-1', isLeaf: true }, { title: '0-0-1-2', key: '0-0-1-2', isLeaf: true } ] }, { title: '0-0-2', key: '0-0-2', isLeaf: true } ] }, { title: '0-1', key: '0-1', children: [ { title: '0-1-0-0', key: '0-1-0-0', isLeaf: true }, { title: '0-1-0-1', key: '0-1-0-1', isLeaf: true }, { title: '0-1-0-2', key: '0-1-0-2', isLeaf: true } ] }, { title: '0-2', key: '0-2', isLeaf: true } ];NzTreeNodeOptions的常用字段及默认值(详见组件文档的 "NzTreeNodeOptions props" 表):
| 字段 | 含义 | 默认值 |
|---|---|---|
title | 节点标题 | '---' |
key | 节点唯一标识,必须唯一 | null |
icon | 节点前图标(配合nzShowIcon) | null |
children | 子节点数组 | [] |
isLeaf | 是否为叶子节点(不可作为拖放目标) | false |
checked/selected/expanded | 数据中直接声明的勾选/选中/展开状态 | false |
selectable | 节点是否可选 | true |
disabled | 是否禁用节点 | false |
disableCheckbox | 是否禁用节点复选框 | false |
[key: string] | 索引签名,允许携带任意自定义字段,通过NzTreeNode.origin读取 | - |
示例中顶层节点'0-0'直接声明了expanded: true,说明状态除了通过受控数组注入外,也能在数据源里预置。
三、底层原理:状态如何从数组落到每一个节点
受控数组并不是简单的"过滤展示",而是由 NzTreeComponent 在ngOnChanges中统一处理——每当任一输入属性变化,都会调用renderTreeProperties(tree.component.ts#L304-L364),按固定顺序处理各类状态:
nzData变化 →handleNzData初始化整棵树;nzCheckedKeys变化 →handleCheckedKeys→conductCheck;nzExpandedKeys/nzExpandAll变化 →handleExpandedKeys→conductExpandedKeys;nzSelectedKeys变化 →handleSelectedKeys→conductSelectedKeys;- 最后把结果扁平化(
flattenTreeData)渲染到列表。
这三个"传导"方法都在基类服务 nz-tree-base.service.ts 中实现:
- 勾选传导
conductCheck(keys, checkStrictly)(nz-tree-base.service.ts#L485-L509):递归遍历所有节点,把keys数组转成Set判断命中,设置每个节点的isChecked与isHalfChecked,最后调用refreshCheckState依据父子关系自动补齐"全选"与"半选"状态。nzCheckStrictly为true时则完全独立,父子互不影响。 - 展开传导
conductExpandedKeys(keys)(nz-tree-base.service.ts#L511-L526):同样以Set判断命中,调用setExpanded并维护展开节点列表。 - 选中传导
conductSelectedKeys(keys, isMulti)(nz-tree-base.service.ts#L528-L549):先清空所有节点的选中状态,再按 key 命中设置isSelected;isMulti为false时命中第一个即停止递归,保证单选语义。
由此可以推断:受控模式下"展开/选中/勾选"三者的状态读写都是围绕NzTreeNode实例(nz-tree-base-node.ts)进行的,key 只是外部与内部之间的"索引协议"。
一个必须知道的顺序约束:先nzData后其他
组件文档在 "Note" 一节明确强调:请确保nzData先被设置,否则其他属性不会生效。因为状态传导是"按 key 对已构建的树做标记",树都没建好,标记自然无处安放。异步接口返回数据后,需要重新赋值一次受控属性以触发渲染(包括nzExpandAll、nzExpandedKeys、nzCheckedKeys、nzSelectedKeys、nzSearchValue)。官方推荐的写法是:
this.nzExpandAll = false; const nodes = []; // 数据源 this.nzData = [...nodes]; // 设置 nzData 之后,若此前已使用过以下属性,需要重新赋值以触发渲染 this.nzExpandedKeys = [...this.nzExpandedKeys]; this.nzCheckedKeys = [...this.nzCheckedKeys]; this.nzSelectedKeys = [...this.nzSelectedKeys];四、事件回传:受控闭环的另一半
受控不能只"进"不"出",nz-tree通过输出事件把交互结果回传给业务层。示例中监听了三个最常用的事件,全部携带统一的NzFormatEmitEvent结构:
| 事件 | 触发时机 | 携带的NzFormatEmitEvent关键字段 |
|---|---|---|
(nzClick) | 点击节点标题 | eventName: 'click'、node、selectedKeys、nodes |
(nzExpandChange) | 节点展开/收起 | eventName: 'expand'、node、keys、nodes |
(nzCheckboxChange) | 点击复选框 | eventName: 'check'、node、checkedKeys、keys、nodes |
NzFormatEmitEvent完整字段(见 tree.component.ts#L249-L263 的输出声明与文档 API 表):
| 字段 | 说明 | 类型 |
|---|---|---|
eventName | 事件名枚举:click / dblclick / contextmenu / check / expand / search / dragstart / dragenter / dragover / dragleave / drop / dragend | 枚举 |
node | 当前操作的节点(如拖放目标节点) | NzTreeNode |
event | 原始MouseEvent或DragEvent | MouseEvent \| DragEvent |
dragNode? | 拖拽中的节点(拖拽事件时存在) | NzTreeNode |
selectedKeys? | 选中的节点列表 | NzTreeNode[] |
checkedKeys? | 勾选的节点列表 | NzTreeNode[] |
matchedKeys? | 搜索命中的节点列表 | NzTreeNode[] |
keys? | 与事件相关的全部节点 key(拖拽事件除外) | string[] |
nodes? | 与事件相关的全部节点(拖拽事件除外) | NzTreeNode[] |
事件分发的实现集中在eventTriggerChanged(tree.component.ts#L426-L486):check事件会先调用setCheckedNodeList更新勾选列表,非nzCheckStrictly模式下再执行conduct联动父子节点,随后以formatEvent('check', node, event)重新包装事件并对外发出,同时还会额外发出nzCheckedKeysChange,方便使用者做"双向绑定"式同步。示例中nzEvent直接console.log(event),正是为了让你在浏览器控制台里直观观察每次点击、展开、勾选后回传的完整结构。
五、源码验证:测试如何锁定受控行为
受控模式的正确性由 components/tree/tree.spec.ts 中的controlled测试套件锁定,这些断言既印证了文档描述,也可作为你调试受控场景的"预期行为清单":
- 初始渲染:
nzExpandedKeys只展开'0-0'时,界面上只显示 3 个节点('0-0'、'0-1'、'0-2'),且每个节点都带复选框(对应basic initial data/should initialize properly用例)。 - 展开联动:把
defaultExpandedKeys改为['0-1']后,可见节点数变为 4,getExpandedNodeList()长度为 1,且"叶节点不会被计入展开列表"(对应should expand the specified node based on nzExpandedKeys)。 - 全展开:设置
nzExpandAll后可见节点数变为 7,展开列表长度为 4(对应should expand all nodes while setting nzExpandAll)。 - 勾选联动:勾选
['0-0-0', '0-0-1']后,0-0自动变为半选(indeterminate),getCheckedNodeList()为 2、getHalfCheckedNodeList()为 1(对应should render checkbox state of nodes based on nzCheckedKeys)。 - 严格模式:
nzCheckStrictly为true时勾选同样的两个节点,不再产生半选节点(对应node check should not affect other nodes based on nzCheckStrictly)。
测试通过querySelectorAll('.ant-tree-checkbox-checked')、.ant-tree-checkbox-indeterminate等 DOM 断言,说明受控状态最终是落到节点渲染上的,验证了"数组 → 节点状态 → 视图"这条完整链路。
六、受控状态读取:组件实例方法
除了通过事件回传,也可以在组件视图初始化后通过@ViewChild(NzTreeComponent)拿到组件实例,调用其公开方法读取受控结果(方法列表见组件文档 "Methods" 一节):
| 方法 | 返回 |
|---|---|
getTreeNodes() | 全部节点NzTreeNode[] |
getTreeNodeByKey(key) | 指定 key 的节点 |
getCheckedNodeList() | 勾选节点(合并父子) |
getSelectedNodeList() | 选中节点 |
getHalfCheckedNodeList() | 半选节点 |
getExpandedNodeList() | 展开节点 |
getMatchedNodeList() | 搜索命中的节点(nzSearchValue非空时) |
注意文档的提示:若在@ViewChild上使用这些方法,应在ngAfterViewInit中调用,因为视图初始化完成后组件实例才就绪。这些方法在NzTreeComponent上通过NzTreeBase暴露,内部实现见 nz-tree-base.service.ts#L82-L105 等。
七、从受控示例到真实业务场景
基于上面的机制,受控模式适合以下典型场景:
- 权限/角色勾选:用
nzCheckedKeys回显已有权限,勾选变化通过nzCheckedKeysChange或nzCheckboxChange收集并提交; - 目录展开记忆:把
nzExpandedKeys持久化(如 localStorage),刷新页面后原样恢复展开层级; - 列表选中回显:用
nzSelectedKeys恢复上次选中项,配合nzClick更新选中态; - 异步加载后的状态恢复:数据接口返回后再赋值
nzExpandedKeys / nzCheckedKeys / nzSelectedKeys(必须遵守"先nzData后状态"的顺序,必要时对状态数组做一次浅拷贝以强制触发变更检测)。
补充两个官方文档明确给出的实用约定:
NzTreeNodeOptions支持携带任意自定义属性,通过NzTreeNode.origin读取(tree.component.ts#L117 处origin定义为"用户提供的原始NzTreeNodeOptions");- 设置
nzData时优先使用NzTreeNodeOptions[]形式,传入NzTreeNode[]将在下个大版本(8.x)被废弃。
八、小结
受控操作是nz-tree最重要的能力之一:nzCheckedKeys、nzExpandedKeys、nzSelectedKeys三个数组输入负责"注入状态",nzClick、nzExpandChange、nzCheckboxChange等事件负责"回传状态",底层由 tree.component.ts 的renderTreeProperties与 nz-tree-base.service.ts 的conduct*系列方法完成"数组 ↔ 节点状态 ↔ 视图"的闭环。掌握本节示例(basic-controlled.ts)及其背后的传导逻辑,你就能在任何需要记忆或回显树状态的业务中游刃有余。
延伸阅读:同一 demo 目录下还有 basic.md(基础非受控用法)、dynamic.md(异步加载)、search.md(搜索高亮与
nzSearchValue)、draggable.md(拖拽)等示例,受控思路可以与之组合使用;完整 API 参考见 components/tree/doc/index.en-US.md。
- UI组件
- 前端
【免费下载链接】ng-zorro-antd
Angular UI Component Library based on Ant Design
相关推荐
ng-zorro-antd 受控 Checkbox 实战:用 Angular Signal 实现组件联动
ng zorro antd 受控 Checkbox 实战:用 Angular Signal 实现组件联动 本文围绕 ng zorro antd(Angular
UI组件前端ng-zorro-antd 树穿梭框(Tree Transfer)实战指南:用 nz-transfer 自定义渲染 Tree 组件
ng zorro antd 树穿梭框(Tree Transfer)实战指南:用 nz transfer 自定义渲染 Tree 组件 导读 本文讲解 ng zor
UI组件前端ng-zorro-antd Mention 组件的 disabled 与 readOnly 状态详解
ng zorro antd Mention 组件的 disabled 与 readOnly 状态详解 nz mention (Mention 提及)是 ng z
UI组件前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考