☰
MateChat Angular 版 mc-list 列表组件:参数、事件与懒加载、快捷键的完整实现解析
2026/10/13 15:57:48 网站建设 项目流程
  • 前端
  • UI组件
  • AI 应用

【免费下载链接】MateChat

前端智能化场景解决方案UI库,轻松构建你的AI应用,我们将持续完善更新,欢迎你的使用与建议。 官网地址:https://matechat.gitcode.com

项目地址:https://gitcode.com/DevCloudFE/MateChat
点击查看免费下载

本篇基于 MateChat 的 Angular 组件库@matechat/ng中的 List 列表组件编写,系统梳理mc-list的全部输入参数、事件、插槽与类型定义,并结合仓库源码还原其选中高亮、懒加载触发阈值与键盘快捷键的实现细节,帮助你在历史对话列表、下拉选择、标签展示等 AI 聊天界面场景中直接落地该组件。

组件定位与引入方式

List 组件(选择器mc-list)用于展示一组内容或选项,适用于历史对话列表呈现、下拉选项、标签展示等场景。它由两部分组成:

  • 组件层:ListComponent(standalone 组件),负责输入绑定、事件派发、键盘监听与模板渲染;
  • 公共层:components-common/List下的 Foundation、类型与样式,负责类名计算与视觉呈现。

从 list.component.ts 可以看到组件声明为selector: 'mc-list',而 list.module.ts 将其包装为ListModule对外导出。引入方式按当前仓库文档的说明:

import { ListModule } from '@matechat/ng';

类型定义(ListItemData、ListDirection、ListVariant)通过 index.ts 一并导出,最终经由 public-api.ts 暴露给包使用者。根据 package.json,当前库版本为 20.1.0,peer 依赖要求@angular/core/@angular/common^20.3.0。

输入参数(Inputs)

以下为文档给出的完整参数表(见 api.md):

参数名类型默认值说明
dataListItemData[][]需要显示的数据
directionListDirection'vertical'选项排列方式,垂直或者水平
autoWrapbooleantrue是否自动换行,仅在水平排列时生效
variantListVariant'transparent'选项的形态,默认无背景色,悬浮和选中高亮
selectablebooleantrue是否可选中,可选中时,点击后选项高亮
enableLazyLoadbooleanfalse是否支持懒加载
enableShortKeybooleanfalse是否支持快捷键选择
inputElHTMLElement--当支持快捷键选择时,监听键盘事件的元素,默认为document

在 list.component.ts#L37-L45 中,上述@Input()声明与文档默认值完全一致。此外源码中还声明了一个文档参数表未列出的输入preSelectIndex(默认值为this.enableShortKey ? 0 : -1),用于记录键盘预选中的下标,它是快捷键机制的内部状态入口,开启enableShortKey后默认预选第一项。

关于direction与autoWrap的生效关系,foundation.ts 的getListClasses()给出了精确结论:

  • direction === 'horizontal'时容器追加mc-list-horizontal类;
  • 水平排列且autoWrap === false时再追加mc-list-nowrap类。

也就是说垂直排列时autoWrap完全不起作用,与文档“仅在水平排列时生效”的说明互相印证。

事件(Outputs)

事件名类型说明
select(e: ListItemData) => void点击选项时触发的事件,参数为用户传入的选项数据
loadMore(e: Event) => void滚动到底时触发的事件,仅在启用懒加载且排列方式为垂直时触发

select 事件:发出的是选项的副本。查看 list.component.ts#L132-L142 的onItemClick实现,组件在派发事件时执行的是this.select.emit({ ...clickedItem }),即对选项做了浅拷贝。这意味着你在业务代码中修改事件参数不会反向污染data里的原始对象;同时该方法在clickedItem.disabled为true时直接return,禁用项点击不会触发任何事件。

loadMore 事件:50px 阈值与方向限制。滚动监听在 list.component.html 的容器div上通过(scroll)="onListScroll($event)"绑定,处理逻辑位于 list.component.ts#L119-L131:

onListScroll(e: Event): void { if (!this.enableLazyLoad || this.direction !== ListDirection.Vertical) { return; } const targetEl = e.target as HTMLElement; const scrollHeight = targetEl.scrollHeight; const clientHeight = targetEl.clientHeight; const scrollTop = targetEl.scrollTop; if (scrollHeight - clientHeight - scrollTop < LazyLoadThreshold) { this.loadMore.emit(e); } }

其中LazyLoadThreshold定义在 list-const.ts#L1,取值为50——即距底部不足 50px 时触发。注意该判断对每次 scroll 事件都会执行,源码层没有“正在加载”节流,业务侧应自行用 loading 标志位或数据总量守卫(后文懒加载示例即展示了这种写法)。

插槽:自定义选项内容

插槽名返回值说明
itemTemplate{ item: ListItemData }自定义选项内容

在 Angular 模板中的实际用法是通过ng-template内容投影,引用名需为item(组件内部以@ContentChild('item')捕获,见 list.component.ts#L51):

<mc-list [data]="options"> <ng-template #item let-item> <i [class]="item.icon" style="margin-right:4px;"></i>{{ item.label }} </ng-template> </mc-list>

从 list.component.html 的渲染分支可以看到插槽与variant的两种配合方式:

  • variant === 'none'时,选项不再包裹mc-list-item容器,模板内容原样输出,悬浮高亮、圆角等样式全部由使用者自控,适合完全自定义场景;
  • 其他 variant 下,模板内容被渲染进mc-list-item的div中,若未提供插槽则回退为默认展示{{ item['label'] }}。

这一分支逻辑与官方 demo(custom-demo.component.html)中“部分自定义”和“完全自定义”两个示例一一对应。

类型定义

ListItemData

interface ListItemData { label: string; // 显示的内容 value: string | number; // 对应的值 disabled?: boolean; // 是否禁用 active?: boolean; // 是否选中 [key: string]: any; // 其他自定义字段 }

源码中的定义见 list-types.ts#L11-L17,与文档一致。label是默认模板展示的内容,value是选中高亮的匹配依据(后文会说明匹配规则),disabled/active为可选状态位,索引签名则允许携带icon等任意业务字段。

ListDirection

enum ListDirection { Horizontal = 'horizontal', Vertical = 'vertical', }

ListVariant

enum ListVariant { Transparent = 'transparent', // 无背景色,悬浮和选中高亮 Filled = 'filled', // 带背景色 Bordered = 'bordered', // 带边框 None = 'none', //无任何样式,完全由用户自定义 }

四种形态的视觉差异由 list.scss 控制:filled使用$devui-gray-form-control-bg背景,bordered使用$devui-dividing-line边框;而悬浮/预选态统一使用$devui-list-item-hover-*变量、激活态使用$devui-list-item-active-*变量,禁用态使用$devui-disabled-*变量——因此组件外观会跟随 DevUI 主题变量自动变化。

源码级实现解析

选中高亮的匹配规则

selectable为true时,onItemClick会遍历data,将value与点击项value相等的项标记为active = true,其余置为false。这说明高亮匹配基于value而非索引或label,当你用同一份value复用于多个选项时会出现多高亮,这是使用上的隐含约束。同时该过程会直接改写传入的data数组元素,配合select事件的浅拷贝派发,形成“状态写回 data、事件发出副本”的模式。

快捷键选择:监听目标元素的确定逻辑

开启enableShortKey后,组件在ngOnInit中调用initListenDom()决定键盘事件的挂载点(list.component.ts#L92-L104):

  1. 未传inputEl:监听document;
  2. 传入的inputEl标签名在['TEXTAREA', 'INPUT'](InputTagNames常量)中:直接监听该元素;
  3. 其他情况:在该元素内部querySelector('textarea') || querySelector('input'),找不到则回退到document。

即inputEl既可以是输入框本身,也可以是包含输入框的容器元素。

按键处理逻辑在onKeyDown(list.component.ts#L143-L161):

  • ArrowUp:preSelectIndex减 1,为 0 时环绕到末项;
  • ArrowDown:preSelectIndex加 1,为末项时环绕到 0;
  • Enter:仅当selectable为true时对data[preSelectIndex]执行与点击完全一致的onItemClick,从而同样派发select事件。

模板中index === preSelectIndex的选项会带mc-list-item-pre-selection类,视觉上与悬浮态一致,形成“键盘移动 → 高亮跟随 → 回车确认”的连贯操作流,这正是文档 demo 中所述“下拉列表场景的连贯性操作”。

需要注意生命周期细节:ngOnDestroy中会调用removeListener()解绑 keydown,但由于源码中addEventListener与removeEventListener分别调用了this.onKeyDown.bind(this)(两次 bind 产生不同函数引用),从源码结构看该解绑可能不会真正生效;如果你的应用存在大量 List 实例频繁销毁重建,从源码结构看值得留意监听器在document上累积的可能性。

懒加载与默认高度

容器样式.mc-list默认width: 100%; max-height: 300px; overflow: auto(list.scss#L3-L7),这是文档 demo 中“组件默认设置列表最大高度为 300px,可通过style或class覆盖”说法的来源。水平排列时容器为display: flex; flex-wrap: wrap; gap: 12px,mc-list-nowrap下改为flex-wrap: nowrap且项不参与伸缩。

实战示例(来自 demo 应用)

以下示例取自仓库内的演示工程 demo-app,均可直接改写进你的业务组件。

基本用法:选中类场景

选项通过disabled设置禁用、active设置选中,默认悬浮高亮、点击选中高亮(basic-demo.component.html、basic-demo.component.ts):

<div class="demo-container"> <mc-list [data]="options" (select)="onSelect($event)"></mc-list> </div>
get options() { return new Array(6).fill(0).map((_item, i) => ({ label: `Option ${i + 1}`, value: i + 1, disabled: i === 3, // 第 4 项禁用 active: i === 1, // 第 2 项初始选中 })); } onSelect(option: any) { console.log('list selected', option); }

水平排列:展示类场景

direction设置排列方式,variant设置形态,autoWrap控制水平换行,selectable="false"禁用点击选中(display-demo.component.html):

<mc-list [data]="options" [selectable]="false" [autoWrap]="autoWrap" [direction]="direction" [variant]="selectedVariant" ></mc-list>

快捷键:与输入框联动

先获取目标输入框元素,再传给inputEl(shortcut-demo.component.html、shortcut-demo.component.ts):

<input type="text" id="list-shortcut-demo-input" [(ngModel)]="inputValue"> <mc-list [data]="options" [enableShortKey]="true" [inputEl]="inputEl" (select)="handleSelect($event)"></mc-list>
ngOnInit() { this.inputEl = document.getElementById('list-shortcut-demo-input') as HTMLInputElement; } handleSelect(evt: any) { this.inputValue = evt.label; // 键盘确认后回显 }

懒加载:滚动加载分页数据

启用enableLazyLoad并在loadMore中做加载守卫(lazyload-demo.component.html、lazyload-demo.component.ts)。注意这里通过style="height:400px"覆盖了默认 300px 最大高度,并用dLoading包裹展示加载态:

<div dLoading [showLoading]="loading"> <mc-list [data]="options" [enableLazyLoad]="true" (loadMore)="onLoadMore($event)" style="height:400px"></mc-list> </div>
loading = false; total = 30; onLoadMore(_option: any) { if (this.loading || this.options.length >= this.total) { return; } this.loading = true; setTimeout(() => { const newData = new Array(10).fill(0).map((_item, i) => ({ label: `Option ${this.options.length + i + 1}`, value: this.options.length + i + 1, })); this.options.push(...newData); this.loading = false; }, 1500); }

该示例同时印证了前文两点:loadMore依赖垂直排列(水平排列时onListScroll直接返回),以及组件自身不做加载节流,loading与总量守卫必须由业务侧提供。

关键文件索引

内容路径
参数/事件/插槽/类型文档docs/components-ng/list/api.md
场景 demo 说明docs/components-ng/list/demo.md
组件实现packages/components-ng/projects/components-ng/src/List/list.component.ts
模板渲染packages/components-ng/projects/components-ng/src/List/list.component.html
模块与导出packages/components-ng/projects/components-ng/src/List/index.ts
类型与常量packages/components-ng/projects/components-ng/src/components-common/List/common/list-types.ts、list-const.ts
样式packages/components-ng/projects/components-ng/src/components-common/List/common/list.scss
  • 前端
  • UI组件
  • AI 应用

【免费下载链接】MateChat

前端智能化场景解决方案UI库,轻松构建你的AI应用,我们将持续完善更新,欢迎你的使用与建议。 官网地址:https://matechat.gitcode.com

项目地址:https://gitcode.com/DevCloudFE/MateChat
点击查看免费下载

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询