news 2026/9/29 7:39:31

ng-zorro-antd Tree 组件受控操作实战:nzCheckedKeys / nzExpandedKeys / nzSelectedKeys 详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ng-zorro-antd Tree 组件受控操作实战:nzCheckedKeys / nzExpandedKeys / nzSelectedKeys 详解
  • UI组件
  • 前端

【免费下载链接】ng-zorro-antd

Angular UI Component Library based on Ant Design

项目地址:https://gitcode.com/gh_mirrors/ng/ng-zorro-antd
点击查看免费下载

本篇文章基于 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),按固定顺序处理各类状态:

  1. nzData变化 →handleNzData初始化整棵树;
  2. nzCheckedKeys变化 →handleCheckedKeys→conductCheck;
  3. nzExpandedKeys/nzExpandAll变化 →handleExpandedKeys→conductExpandedKeys;
  4. nzSelectedKeys变化 →handleSelectedKeys→conductSelectedKeys;
  5. 最后把结果扁平化(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或DragEventMouseEvent \| 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 等。

七、从受控示例到真实业务场景

基于上面的机制,受控模式适合以下典型场景:

  1. 权限/角色勾选:用nzCheckedKeys回显已有权限,勾选变化通过nzCheckedKeysChange或nzCheckboxChange收集并提交;
  2. 目录展开记忆:把nzExpandedKeys持久化(如 localStorage),刷新页面后原样恢复展开层级;
  3. 列表选中回显:用nzSelectedKeys恢复上次选中项,配合nzClick更新选中态;
  4. 异步加载后的状态恢复:数据接口返回后再赋值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

项目地址:https://gitcode.com/gh_mirrors/ng/ng-zorro-antd
点击查看免费下载
上一篇:**探索知识的无限边界:将Jupyter的魅力融入Obsidian笔记**
下一篇:AutoAnimate: 一键赋予网页动态之美

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 7:36:02

PSCAD线路电缆组件tline_cable_comp建模与参数设置详解

PSCAD里那一堆线路和电缆元件&#xff0c;很多新手一打开就懵了。特别是看到tline_cable_comp这种封装好的组合组件&#xff0c;英文说明书里全是Bergeron、Frequency Dependent、Surge Impedance这类术语&#xff0c;查词典都查得脑壳疼。我自己刚开始做输电线路暂态仿真的时候…

作者头像 李华
网站建设 2026/9/29 7:34:29

用Model-Optimizer打造一键式模型优化流水线:量化、剪枝与蒸馏实战

1. 为什么非要自己造一个Model-Optimizer&#xff1f;1.1 缺的不是优化方法&#xff0c;而是一套顺手的工作流大概从三年前开始&#xff0c;我几乎每个部署项目都会被同一类问题卡住&#xff1a;手里的模型在GPU上跑得好好的&#xff0c;精度也达标&#xff0c;但要搬到工控机、…

作者头像 李华
网站建设 2026/9/29 7:33:02

自动化清理C盘临时文件:一套安全可靠的PowerShell脚本方案

“C盘又红了”——这句话大概是开发者群里出现频率最高的抱怨之一。我自己的主力开发机&#xff0c;明明装了不少软件&#xff0c;可隔三差五就得面对磁盘空间告急的弹窗。起初我也习惯性地打开“磁盘清理”点两下&#xff0c;后来发现真正占地方的临时文件分布在几十个目录里&…

作者头像 李华
网站建设 2026/9/29 7:32:57

Jetson Orin NX对接联适R70M-GNSS串口定位实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 7:31:59

CRC32查表法实战:嵌入式通信校验的硬核落地指南

1. 为什么CRC32查表法是嵌入式与通信开发绕不开的硬功夫你写过串口协议解析&#xff0c;调试过Modbus从机&#xff0c;或者给STM32加过OTA校验——只要数据要跨设备、跨线缆、跨时间传输&#xff0c;就躲不开CRC校验。而CRC32&#xff0c;尤其是查表法实现的CRC32&#xff0c;不…

作者头像 李华
网站建设 2026/9/29 7:31:56

从CAN到车载以太网:汽车电子电气通信网络演进深度解析

先放个反直觉的结论&#xff1a;决定一辆车智能化上限的&#xff0c;往往不是那颗标称上百TOPS的芯片&#xff0c;而是藏在车身内部的通信网络。电子电气架构这些年一直在喊域集中、中央计算&#xff0c;最后真正捅破窗户纸的&#xff0c;是通信协议从“信号矩阵”到“服务调用…

作者头像 李华