设计思路
- Slot 实现内容灵活插入:
- 在 Svelte 中,
<slot>
元素允许在组件内部定义可插入内容的位置。对于树形菜单组件,我们可以在每个菜单节点组件中使用 <slot>
来允许用户插入自定义的节点内容,比如节点的文本、图标等。
- Action 实现动态交互:
- 展开/收缩动画:通过 Svelte 的 Action,可以在节点展开和收缩时添加 CSS 过渡动画。例如,当节点展开时,使用
transition: height 0.3s ease;
来实现高度变化的平滑过渡。
- 鼠标悬停特效:同样利用 Action,当鼠标悬停在节点上时,添加相应的 CSS 样式变化,如改变背景颜色或字体颜色等。
- 数据与状态共享:
- 父子组件:可以通过 props 将数据从父组件传递到子组件。对于树形菜单,父组件可以传递节点数据、当前展开状态等。同时,子组件可以通过
createEventDispatcher
向父组件发送事件,比如节点展开/收缩的事件,以便父组件更新状态。
- 兄弟组件:一种方式是通过一个共享的上下文(Context API)来共享数据和状态。例如,创建一个树形菜单的上下文,包含当前展开的节点信息等,兄弟节点可以从中获取和更新相关状态。另一种方式是通过父组件作为中间人,子组件将事件发送给父组件,父组件再将更新后的状态传递给其他子组件。
- 大规模数据下的性能优化:
- 虚拟列表:对于大规模数据,可以使用虚拟列表技术,只渲染当前可见的节点,减少 DOM 操作和内存占用。
- 防抖/节流:在处理频繁触发的事件(如鼠标悬停)时,使用防抖或节流技术,避免不必要的计算和渲染。
关键代码片段
- 树形菜单父组件(Tree.svelte):
<script>
import TreeNode from './TreeNode.svelte';
let treeData = [
{ id: 1, label: 'Node 1', children: [
{ id: 11, label: 'Node 1.1' },
{ id: 12, label: 'Node 1.2' }
]},
{ id: 2, label: 'Node 2' }
];
let expandedNodes = [];
const handleNodeToggle = (nodeId) => {
if (expandedNodes.includes(nodeId)) {
expandedNodes = expandedNodes.filter(id => id!== nodeId);
} else {
expandedNodes.push(nodeId);
}
};
</script>
{#each treeData as node}
<TreeNode {node} {expandedNodes} on:toggle={handleNodeToggle}>
{node.label}
</TreeNode>
{/each}
- 树形菜单节点组件(TreeNode.svelte):
<script>
import { fade, slide } from'svelte/transition';
export let node;
export let expandedNodes;
const dispatch = createEventDispatcher();
const toggleNode = () => {
dispatch('toggle', node.id);
};
let isExpanded = expandedNodes.includes(node.id);
</script>
<div class="tree - node" on:click={toggleNode}>
<slot />
{#if node.children && isExpanded}
<div class="children" in:slide={{ duration: 300 }} out:slide={{ duration: 300 }}>
{#each node.children as child}
<TreeNode {child} {expandedNodes} on:toggle={toggleNode}>
{child.label}
</TreeNode>
{/each}
</div>
{/if}
</div>
<style>
.tree - node {
cursor: pointer;
padding: 5px;
}
.tree - node:hover {
background - color: lightgray;
}
.children {
padding - left: 20px;
}
</style>
- Action 实现鼠标悬停特效(可在 TreeNode.svelte 中添加):
<script>
import { fade, slide } from'svelte/transition';
export let node;
export let expandedNodes;
const dispatch = createEventDispatcher();
const toggleNode = () => {
dispatch('toggle', node.id);
};
let isExpanded = expandedNodes.includes(node.id);
const hoverAction = (node) => {
const handleMouseOver = () => {
node.style.backgroundColor = 'lightblue';
};
const handleMouseOut = () => {
node.style.backgroundColor = 'white';
};
node.addEventListener('mouseover', handleMouseOver);
node.addEventListener('mouseout', handleMouseOut);
return {
destroy() {
node.removeEventListener('mouseover', handleMouseOver);
node.removeEventListener('mouseout', handleMouseOut);
}
};
};
</script>
<div class="tree - node" use: hoverAction on:click={toggleNode}>
<slot />
{#if node.children && isExpanded}
<div class="children" in:slide={{ duration: 300 }} out:slide={{ duration: 300 }}>
{#each node.children as child}
<TreeNode {child} {expandedNodes} on:toggle={toggleNode}>
{child.label}
</TreeNode>
{/each}
</div>
{/if}
</div>
<style>
.tree - node {
cursor: pointer;
padding: 5px;
}
.children {
padding - left: 20px;
}
</style>