列表渲染 #
一、基本语法 #
1.1 each 块 #
svelte
<script>
let items = ['Apple', 'Banana', 'Orange'];
</script>
<ul>
{#each items as item}
<li>{item}</li>
{/each}
</ul>
1.2 带索引的 each #
svelte
<script>
let items = ['Apple', 'Banana', 'Orange'];
</script>
<ul>
{#each items as item, index}
<li>{index + 1}. {item}</li>
{/each}
</ul>
1.3 对象数组 #
svelte
<script>
let users = [
{ id: 1, name: 'Alice', age: 25 },
{ id: 2, name: 'Bob', age: 30 },
{ id: 3, name: 'Charlie', age: 35 }
];
</script>
<ul>
{#each users as user}
<li>{user.name} ({user.age})</li>
{/each}
</ul>
二、Key 的使用 #
2.1 为什么需要 Key #
text
┌─────────────────────────────────────────────────────────────┐
│ Key 的作用 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 无 Key(按索引匹配) │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ A 0 │ │ B 1 │ │ C 2 │ │
│ └─────┘ └─────┘ └─────┘ │
│ ↓ ↓ ↓ │
│ 删除 B 后: │
│ ┌─────┐ ┌─────┐ │
│ │ A 0 │ │ C 1 │ ← C 移动到索引1,状态可能错乱 │
│ └─────┘ └─────┘ │
│ │
│ 有 Key(按唯一标识匹配) │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ A 1 │ │ B 2 │ │ C 3 │ │
│ └─────┘ └─────┘ └─────┘ │
│ ↓ ↓ │
│ 删除 B 后: │
│ ┌─────┐ ┌─────┐ │
│ │ A 1 │ │ C 3 │ ← C 保持正确,状态正确 │
│ └─────┘ └─────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
2.2 使用 Key #
svelte
<script>
let items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
];
</script>
<ul>
{#each items as item (item.id)}
<li>{item.name}</li>
{/each}
</ul>
2.3 Key 的正确使用 #
svelte
<script>
let todos = $state([
{ id: 1, text: 'Learn Svelte', done: false },
{ id: 2, text: 'Build an app', done: false },
{ id: 3, text: 'Deploy', done: false }
]);
function removeTodo(id) {
todos = todos.filter(t => t.id !== id);
}
function toggleTodo(id) {
todos = todos.map(t =>
t.id === id ? { ...t, done: !t.done } : t
);
}
</script>
<ul>
{#each todos as todo (todo.id)}
<li class:done={todo.done}>
<input
type="checkbox"
checked={todo.done}
onchange={() => toggleTodo(todo.id)}
/>
{todo.text}
<button onclick={() => removeTodo(todo.id)}>×</button>
</li>
{/each}
</ul>
三、嵌套循环 #
3.1 双层嵌套 #
svelte
<script>
let categories = [
{
name: 'Fruits',
items: ['Apple', 'Banana', 'Orange']
},
{
name: 'Vegetables',
items: ['Carrot', 'Broccoli', 'Spinach']
}
];
</script>
{#each categories as category}
<div class="category">
<h3>{category.name}</h3>
<ul>
{#each category.items as item}
<li>{item}</li>
{/each}
</ul>
</div>
{/each}
3.2 带索引的嵌套 #
svelte
<script>
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
</script>
<table>
{#each matrix as row, rowIndex}
<tr>
{#each row as cell, colIndex}
<td>
[{rowIndex},{colIndex}]: {cell}
</td>
{/each}
</tr>
{/each}
</table>
3.3 复杂数据结构 #
svelte
<script>
let departments = [
{
id: 1,
name: 'Engineering',
teams: [
{ id: 1, name: 'Frontend', members: ['Alice', 'Bob'] },
{ id: 2, name: 'Backend', members: ['Charlie', 'David'] }
]
},
{
id: 2,
name: 'Design',
teams: [
{ id: 3, name: 'UI/UX', members: ['Eve', 'Frank'] }
]
}
];
</script>
{#each departments as dept (dept.id)}
<div class="department">
<h2>{dept.name}</h2>
{#each dept.teams as team (team.id)}
<div class="team">
<h3>{team.name}</h3>
<ul>
{#each team.members as member}
<li>{member}</li>
{/each}
</ul>
</div>
{/each}
</div>
{/each}
四、列表操作 #
4.1 添加项目 #
svelte
<script>
let items = $state([]);
let newItem = $state('');
function addItem() {
if (newItem.trim()) {
items = [...items, {
id: Date.now(),
text: newItem.trim()
}];
newItem = '';
}
}
</script>
<input
bind:value={newItem}
onkeydown={(e) => e.key === 'Enter' && addItem()}
/>
<button onclick={addItem}>Add</button>
<ul>
{#each items as item (item.id)}
<li>{item.text}</li>
{/each}
</ul>
4.2 删除项目 #
svelte
<script>
let items = $state([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
]);
function removeItem(id) {
items = items.filter(item => item.id !== id);
}
</script>
<ul>
{#each items as item (item.id)}
<li>
{item.text}
<button onclick={() => removeItem(item.id)}>×</button>
</li>
{/each}
</ul>
4.3 更新项目 #
svelte
<script>
let items = $state([
{ id: 1, text: 'Item 1', done: false },
{ id: 2, text: 'Item 2', done: false }
]);
function toggleItem(id) {
items = items.map(item =>
item.id === id
? { ...item, done: !item.done }
: item
);
}
function updateText(id, text) {
items = items.map(item =>
item.id === id ? { ...item, text } : item
);
}
</script>
<ul>
{#each items as item (item.id)}
<li class:done={item.done}>
<input
type="checkbox"
checked={item.done}
onchange={() => toggleItem(item.id)}
/>
<input
type="text"
value={item.text}
oninput={(e) => updateText(item.id, e.target.value)}
/>
</li>
{/each}
</ul>
4.4 排序和过滤 #
svelte
<script>
let items = $state([
{ id: 1, name: 'Banana', price: 2 },
{ id: 2, name: 'Apple', price: 1 },
{ id: 3, name: 'Orange', price: 3 }
]);
let sortBy = $state('name');
let filterText = $state('');
let filteredItems = $derived(() => {
let result = items.filter(item =>
item.name.toLowerCase().includes(filterText.toLowerCase())
);
result.sort((a, b) => {
if (a[sortBy] < b[sortBy]) return -1;
if (a[sortBy] > b[sortBy]) return 1;
return 0;
});
return result;
});
</script>
<input bind:value={filterText} placeholder="Filter..." />
<select bind:value={sortBy}>
<option value="name">Name</option>
<option value="price">Price</option>
</select>
<ul>
{#each filteredItems() as item (item.id)}
<li>{item.name} - ${item.price}</li>
{/each}
</ul>
五、空列表处理 #
5.1 else 块 #
svelte
<script>
let items = $state([]);
</script>
{#each items as item (item.id)}
<div>{item.name}</div>
{:else}
<div class="empty">No items found</div>
{/each}
5.2 加载状态 #
svelte
<script>
let items = $state([]);
let loading = $state(true);
async function loadItems() {
loading = true;
try {
const response = await fetch('/api/items');
items = await response.json();
} finally {
loading = false;
}
}
loadItems();
</script>
{#if loading}
<div class="loading">Loading...</div>
{:else if items.length === 0}
<div class="empty">No items</div>
{:else}
{#each items as item (item.id)}
<div>{item.name}</div>
{/each}
{/if}
六、列表过渡动画 #
6.1 简单过渡 #
svelte
<script>
import { fade, fly, slide } from 'svelte/transition';
let items = $state([]);
function addItem() {
items = [...items, { id: Date.now(), text: 'New Item' }];
}
function removeItem(id) {
items = items.filter(item => item.id !== id);
}
</script>
<button onclick={addItem}>Add Item</button>
<ul>
{#each items as item (item.id)}
<li transition:fade>
{item.text}
<button onclick={() => removeItem(item.id)}>×</button>
</li>
{/each}
</ul>
6.2 FLIP 动画 #
svelte
<script>
import { flip } from 'svelte/animate';
import { fade } from 'svelte/transition';
let items = $state([
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
]);
function shuffle() {
items = items.sort(() => Math.random() - 0.5);
}
</script>
<button onclick={shuffle}>Shuffle</button>
<ul>
{#each items as item (item.id)}
<li animate:flip transition:fade>
{item.text}
</li>
{/each}
</ul>
七、实际应用示例 #
7.1 可排序列表 #
svelte
<script>
let items = $state([
{ id: 1, name: 'Item 1', order: 0 },
{ id: 2, name: 'Item 2', order: 1 },
{ id: 3, name: 'Item 3', order: 2 }
]);
function moveUp(index) {
if (index > 0) {
const newItems = [...items];
[newItems[index - 1], newItems[index]] =
[newItems[index], newItems[index - 1]];
items = newItems;
}
}
function moveDown(index) {
if (index < items.length - 1) {
const newItems = [...items];
[newItems[index], newItems[index + 1]] =
[newItems[index + 1], newItems[index]];
items = newItems;
}
}
</script>
<ul>
{#each items as item, index (item.id)}
<li>
{item.name}
<button onclick={() => moveUp(index)} disabled={index === 0}>↑</button>
<button onclick={() => moveDown(index)} disabled={index === items.length - 1}>↓</button>
</li>
{/each}
</ul>
7.2 分页列表 #
svelte
<script>
let allItems = $state(Array.from({ length: 100 }, (_, i) => ({
id: i + 1,
name: `Item ${i + 1}`
})));
let pageSize = $state(10);
let currentPage = $state(1);
let totalPages = $derived(Math.ceil(allItems.length / pageSize));
let paginatedItems = $derived(
allItems.slice(
(currentPage - 1) * pageSize,
currentPage * pageSize
)
);
function goToPage(page) {
currentPage = Math.min(Math.max(1, page), totalPages);
}
</script>
<ul>
{#each paginatedItems as item (item.id)}
<li>{item.name}</li>
{/each}
</ul>
<div class="pagination">
<button onclick={() => goToPage(1)} disabled={currentPage === 1}>First</button>
<button onclick={() => goToPage(currentPage - 1)} disabled={currentPage === 1}>Prev</button>
{#each Array.from({ length: totalPages }, (_, i) => i + 1) as page}
<button
class:active={page === currentPage}
onclick={() => goToPage(page)}
>
{page}
</button>
{/each}
<button onclick={() => goToPage(currentPage + 1)} disabled={currentPage === totalPages}>Next</button>
<button onclick={() => goToPage(totalPages)} disabled={currentPage === totalPages}>Last</button>
</div>
7.3 可展开列表 #
svelte
<script>
let items = $state([
{
id: 1,
title: 'Item 1',
content: 'Content for item 1',
expanded: false
},
{
id: 2,
title: 'Item 2',
content: 'Content for item 2',
expanded: false
}
]);
function toggle(id) {
items = items.map(item =>
item.id === id
? { ...item, expanded: !item.expanded }
: item
);
}
</script>
<div class="accordion">
{#each items as item (item.id)}
<div class="item">
<div class="header" onclick={() => toggle(item.id)}>
<span>{item.title}</span>
<span>{item.expanded ? '−' : '+'}</span>
</div>
{#if item.expanded}
<div class="content">
{item.content}
</div>
{/if}
</div>
{/each}
</div>
八、性能优化 #
8.1 虚拟列表 #
svelte
<script>
let allItems = Array.from({ length: 10000 }, (_, i) => ({
id: i + 1,
name: `Item ${i + 1}`
}));
let scrollTop = $state(0);
let containerHeight = 400;
let itemHeight = 40;
let visibleCount = Math.ceil(containerHeight / itemHeight) + 2;
let startIndex = $derived(Math.floor(scrollTop / itemHeight));
let endIndex = $derived(Math.min(startIndex + visibleCount, allItems.length));
let visibleItems = $derived(
allItems.slice(startIndex, endIndex)
);
let totalHeight = allItems.length * itemHeight;
let offsetY = $derived(startIndex * itemHeight);
function handleScroll(e) {
scrollTop = e.target.scrollTop;
}
</script>
<div class="virtual-list" onscroll={handleScroll} style="height: {containerHeight}px">
<div style="height: {totalHeight}px; position: relative;">
<div style="position: absolute; top: {offsetY}px; width: 100%;">
{#each visibleItems as item (item.id)}
<div class="item" style="height: {itemHeight}px">
{item.name}
</div>
{/each}
</div>
</div>
</div>
8.2 避免不必要的重新渲染 #
svelte
<script>
let items = $state([]);
let filter = $state('');
let filteredItems = $derived(
items.filter(item => item.name.includes(filter))
);
</script>
{#each filteredItems as item (item.id)}
<Item {item} />
{/each}
九、总结 #
| 语法 | 说明 |
|---|---|
{#each items as item} |
基本循环 |
{#each items as item, index} |
带索引 |
{#each items as item (key)} |
带 Key |
{:else} |
空列表处理 |
{/each} |
循环结束 |
列表渲染要点:
- 使用
(key)指定唯一标识 - 嵌套循环时注意 key 的唯一性
- 使用
{:else}处理空列表 - 大列表考虑虚拟滚动
- 合理使用过渡动画提升体验
最后更新:2026-03-28