TreeView 进阶功能
基础 TreeView 能展示数据,进阶功能让树「活」起来:监听展开/折叠、程序化定位节点、支持拖拽、按需加载大数据量。
展开/折叠监听
createTreeView 返回的视图对象支持事件监听:
typescript
import * as vscode from 'vscode';
const treeView = vscode.window.createTreeView('myExt.tree', {
treeDataProvider: provider
});
// 展开监听
treeView.onDidExpandElement((event) => {
const node = event.element;
console.log(`展开: ${node.label}`);
// 可以在展开时懒加载数据
});
// 折叠监听
treeView.onDidCollapseElement((event) => {
const node = event.element;
console.log(`折叠: ${node.label}`);
});应用场景
| 事件 | 用途 |
|---|---|
onDidExpandElement | 懒加载子节点、统计访问 |
onDidCollapseElement | 清理子节点缓存 |
onDidChangeSelection | 选中变化联动详情 |
reveal 定位到元素
程序化滚动并选中指定节点:
typescript
// 定位并展开节点
await treeView.reveal(node, {
expand: true, // 展开父节点
focus: true, // 聚焦
select: true // 选中
});| 选项 | 作用 |
|---|---|
expand | 是否展开祖先节点 |
focus | 是否聚焦视图 |
select | 是否选中节点 |
定位时机
- 搜索跳转:搜索结果选中对应树节点
- 命令跳转:执行命令后定位到相关资源
- 刷新保持:刷新后恢复之前选中
拖拽支持
启用拖拽需在创建视图时配置:
typescript
const treeView = vscode.window.createTreeView('myExt.tree', {
treeDataProvider: provider,
dragAndDrop: true, // 允许拖拽
canSelectMany: true // 支持多选
});TreeDragAndDrop 接口
typescript
class DragAndDropProvider implements vscode.TreeDragAndDropController<Node> {
// 拖拽 MIME 类型
readonly dropMimeTypes = ['application/vnd.code.tree.myExt.tree'];
readonly dragMimeTypes = ['application/vnd.code.tree.myExt.tree'];
// 开始拖拽:返回拖拽数据
async handleDrag(
source: readonly Node[],
treeDataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken
): Promise<void> {
// 序列化拖拽数据
treeDataTransfer.set(
'application/vnd.code.tree.myExt.tree',
new vscode.DataTransferItem(source)
);
}
// 放下处理
async handleDrop(
target: Node | undefined,
treeDataTransfer: vscode.DataTransfer,
token: vscode.CancellationToken
): Promise<void> {
// 读取拖拽数据
const item = treeDataTransfer.get(
'application/vnd.code.tree.myExt.tree'
);
if (!item) {
return;
}
const dragged = item.value as Node[];
// 执行移动逻辑
await this.moveNodes(dragged, target);
}
}拖拽 MIME 类型约定
application/vnd.code.tree.<视图ID>过滤与搜索
内建搜索框
contributes.views 声明搜索支持:
json
{
"views": {
"myExt": [
{
"id": "myExt.tree",
"name": "项目资源",
"contextualTitle": "资源搜索",
"when": "!config.myExt.disableSearch"
}
]
}
}自定义过滤
typescript
class FilterableProvider implements vscode.TreeDataProvider<Node> {
private filterText = '';
setFilter(text: string) {
this.filterText = text.toLowerCase();
this.refresh();
}
async getChildren(node?: Node): Promise<Node[]> {
const children = await this.getAllChildren(node);
if (!this.filterText) {
return children;
}
// 过滤匹配节点
return children.filter((child) =>
child.label.toLowerCase().includes(this.filterText)
);
}
}虚拟化性能优化
大数据量(上万节点)场景:
1. 懒加载
只在展开时加载子节点:
typescript
getChildren(node?: Node) {
if (!node) {
return this.loadRoots();
}
// 按需加载,避免一次性加载全部
return this.loadChildren(node);
}2. 缓存子节点
typescript
class Node {
private cachedChildren?: Node[];
async getChildren(): Promise<Node[]> {
if (!this.cachedChildren) {
this.cachedChildren = await this.loadChildren();
}
return this.cachedChildren;
}
}3. 轻量 TreeItem
避免在 TreeItem 中附加大对象:
typescript
// 只保留显示所需最小数据
class Node extends vscode.TreeItem {
constructor(label: string, state: vscode.TreeItemCollapsibleState) {
super(label, state);
// 不持有完整数据,getChildren 时再加载
}
}4. 局部刷新
typescript
// 只刷新变化的节点,而非整树
this._onDidChangeTreeData.fire(changedNode);完整示例:懒加载目录树
typescript
import * as vscode from 'vscode';
class LazyNode extends vscode.TreeItem {
private childrenCache: LazyNode[] | undefined;
constructor(
label: string,
state: vscode.TreeItemCollapsibleState,
public readonly uri: vscode.Uri
) {
super(label, state);
}
// 懒加载:首次访问才读取目录
async getChildren(): Promise<LazyNode[]> {
if (this.childrenCache) {
return this.childrenCache;
}
this.childrenCache = [];
const entries = await vscode.workspace.fs.readDirectory(this.uri);
for (const [name, type] of entries) {
const isDir = type === vscode.FileType.Directory;
this.childrenCache.push(
new LazyNode(
name,
isDir
? vscode.TreeItemCollapsibleState.Collapsed
: vscode.TreeItemCollapsibleState.None,
vscode.Uri.joinPath(this.uri, name)
)
);
}
return this.childrenCache;
}
}
class LazyTreeProvider implements vscode.TreeDataProvider<LazyNode> {
getTreeItem(element: LazyNode): vscode.TreeItem {
return element;
}
async getChildren(element?: LazyNode): Promise<LazyNode[]> {
if (!element) {
// 根节点:工作区文件夹
const folders = vscode.workspace.workspaceFolders ?? [];
return folders.map((folder) =>
new LazyNode(
folder.name,
vscode.TreeItemCollapsibleState.Expanded,
folder.uri
)
);
}
return element.getChildren();
}
}性能优化对比
| 策略 | 适用场景 | 效果 |
|---|---|---|
| 懒加载 | 深层级数据 | 显著减少初始加载 |
| 子节点缓存 | 频繁展开相同节点 | 减少 IO |
| 局部刷新 | 少量节点变化 | 避免整树重绘 |
| 轻量 TreeItem | 超大数据集 | 降低内存 |
常见问题
| 问题 | 处理 |
|---|---|
| reveal 定位不到 | 先确认节点存在且 getChildren 已加载 |
| 拖拽不生效 | 检查 MIME 类型与视图 ID 一致 |
| 大列表卡顿 | 启用懒加载与缓存 |
| 展开事件不触发 | 确认使用 createTreeView 方式注册 |
TreeView 进阶能力让树视图支持真实业务场景:按需加载、交互增强、性能可控,是构建生产级数据面板的关键。