文件树(File Explorer)是代码编辑器的核心组件之一,本文将介绍文件树的数据结构设计、虚拟文件系统与交互实现。
树节点数据结构
首先定义文件树节点的数据结构:
file-tree-model.js
// 文件树节点类型 const FileType = { FILE: 'file', DIRECTORY: 'directory' }; // 树节点数据结构 class TreeNode { constructor(name, type, path) { this.name = name; this.type = type; // FileType.FILE 或 DIRECTORY this.path = path; // 完整路径 this.children = []; // 子节点 this.parent = null; this.expanded = false; // 展开状态 this.depth = 0; // 深度(用于缩进) this.icon = this.getIcon(); } getIcon() { if (this.type === FileType.DIRECTORY) { return this.expanded ? '📂' : '📁'; } // 根据扩展名返回文件图标 const ext = this.name.split('.').pop(); const icons = { js: '📜', ts: '📘', py: '🐍', html: '🌐', css: '🎨', json: '📋' }; return icons[ext] || '📄'; } isFolder() { return this.type === FileType.DIRECTORY; } }
虚拟文件系统
在浏览器中实现虚拟文件系统来管理项目文件:
virtual-fs.js
// 虚拟文件系统 class VirtualFileSystem { constructor() { this.root = new TreeNode('root', FileType.DIRECTORY, '/'); this.files = new Map(); // path -> TreeNode this.files.set('/', this.root); } // 创建文件或目录 create(path, type, content = '') { const parts = path.split('/').filter(p => p); let current = this.root; let currentPath = '/'; for (let i = 0; i < parts.length; i++) { const part = parts[i]; const isLast = i === parts.length - 1; currentPath += part + '/'; let child = current.children.find(c => c.name === part); if (!child) { child = new TreeNode( part, isLast ? type : FileType.DIRECTORY, currentPath ); child.parent = current; child.depth = i; current.children.push(child); this.files.set(currentPath, child); } current = child; } return current; } // 读取文件内容 readFile(path) { const node = this.files.get(path); if (node && node.type === FileType.FILE) { return node.content || ''; } return null; } // 写入文件内容 writeFile(path, content) { const node = this.files.get(path); if (node && node.type === FileType.FILE) { node.content = content; } } // 获取目录下的所有直接子节点 getChildren(path) { const node = this.files.get(path); if (node && node.type === FileType.DIRECTORY) { return node.children; } return []; } }
文件树渲染
使用递归方式渲染文件树:
file-tree-render.js
// 渲染文件树 class FileTreeRenderer { constructor(container, vfs) { this.container = container; this.vfs = vfs; } render() { this.container.innerHTML = ''; this.renderNode(this.vfs.root, this.container); } renderNode(node, parent) { const element = this.createNodeElement(node); parent.appendChild(element); // 如果是展开的目录,递归渲染子节点 if (node.expanded && node.children.length > 0) { const childContainer = document.createElement('div'); childContainer.className = 'tree-children'; childContainer.style.paddingLeft = '16px'; parent.appendChild(childContainer); for (const child of node.children) { this.renderNode(child, childContainer); } } } createNodeElement(node) { const el = document.createElement('div'); el.className = 'tree-node'; el.style.display = 'flex'; el.style.alignItems = 'center'; el.style.padding = '6px 8px'; el.style.cursor = 'pointer'; el.style.userSelect = 'none'; // 展开/折叠图标 const chevron = document.createElement('span'); chevron.className = 'tree-chevron'; chevron.textContent = node.isFolder() ? (node.expanded ? '▼' : '▶') : ' '; chevron.style.width = '16px'; chevron.style.fontSize = '10px'; chevron.style.marginRight = '6px'; el.appendChild(chevron); // 文件/文件夹图标 const icon = document.createElement('span'); icon.textContent = node.icon; icon.style.marginRight = '8px'; el.appendChild(icon); // 文件名 const name = document.createElement('span'); name.textContent = node.name; name.className = 'tree-name'; el.appendChild(name); // 绑定点击事件 el.addEventListener('click', () => this.onNodeClick(node)); return el; } onNodeClick(node) { if (node.isFolder()) { node.expanded = !node.expanded; this.render(); } else { // 打开文件 openFile(node.path); } } }
文件操作
实现右键菜单进行文件操作:
file-operations.js
// 文件树上下文菜单 class ContextMenu { constructor(tree) { this.tree = tree; } show(x, y, node) { const menu = document.createElement('div'); menu.className = 'context-menu'; menu.style.position = 'fixed'; menu.style.left = x + 'px'; menu.style.top = y + 'px'; menu.style.zIndex = '1000'; const actions = [ { label: '新建文件', action: ()=>this.newFile(node) }, { label: '新建文件夹', action: ()=>this.newFolder(node) }, { label: '重命名', action: ()=>this.rename(node) }, { label: '删除', action: ()=>this.delete(node) } ]; for (const {label, action} of actions) { const item = document.createElement('div'); item.className = 'context-item'; item.textContent = label; item.addEventListener('click', ()=>{ action(); menu.remove(); }); menu.appendChild(item); } document.body.appendChild(menu); } newFile(node) { const name = prompt('请输入文件名:'); if (name) { const path = node.isFolder() ? node.path + name : node.path + '/' + name; this.tree.vfs.create(path, FileType.FILE); this.tree.render(); } } }
总结
文件树组件的实现关键在于:树形数据结构的清晰设计、虚拟文件系统对文件操作的支持,以及高效的递归渲染机制。结合右键菜单,可以提供完整的项目管理体验。