Vòng lặp trong Svelte: Tạo Todo App với {#each}
Giới thiệu
Bạn có nhớ lần cuối cùng bạn thấy một ứng dụng chỉ hiển thị một thứ? Có thể là một todo trong ứng dụng todo của bạn, một email trong hộp thư đến, hoặc chỉ một meme trong nguồn cấp dữ liệu của bạn. Điều đó thật sự buồn tẻ. 😂
Ứng dụng thực tế thường xử lý danh sách — hàng chục, hàng trăm, thậm chí hàng nghìn mục. Thay vì sao chép và dán hàng trăm lần, chúng ta muốn máy tính tự động làm việc nhàm chán đó cho chúng ta.
Đó là lý do tại sao vòng lặp Svelte rất hữu ích. Giống như một máy photocopy 📠: bạn thiết kế một mẫu, sau đó Svelte tự động sao chép bao nhiêu tùy thích.
Trong bài viết này, bạn sẽ học cách:
- Lặp lại các phần tử với
{#each}
. - Lấy các thuộc tính dễ dàng bằng cách sử dụng destructuring.
- Sử dụng chỉ số khi cần “đầu tiên, thứ hai, thứ ba.”
- Xử lý các mảng rỗng một cách tinh tế.
- Lồng các vòng lặp để tạo danh mục và các mục con.
Cuối cùng, bạn sẽ sẵn sàng để hiển thị mọi thứ từ một danh sách todo nhỏ ✅ đến một giỏ hàng khổng lồ 🛒 — mà không cảm thấy căng thẳng.
Khối {#each}
Vòng lặp đơn giản nhất trong Svelte là {#each}
. Hãy cùng hiển thị một danh sách trái cây.
Ví dụ 1: Danh sách trái cây
svelte
<script>
let fruits = $state(['Apple', 'Banana', 'Cherry']);
</script>
<ul>
{#each fruits as fruit}
<li>{fruit}</li>
{/each}
</ul>
Phân tích
let fruits = $state([...])
: Một mảng phản ứng chứa tên các loại trái cây.{#each fruits as fruit}
: Lặp qua từng mục trong mảng, gọi mỗi mục làfruit
.<li>{fruit}</li>
: Hiển thị trái cây trong một mục danh sách.
👉 Kết quả: Một danh sách không có thứ tự với Apple, Banana, Cherry.
Destructuring trong Vòng lặp
Mảng không chỉ chứa chuỗi. Đôi khi chúng chứa các đối tượng với nhiều thuộc tính. {#each}
có thể destructure chúng trực tiếp.
Ví dụ 2: Danh sách Todo
svelte
<script>
let todos = $state([
{ id: 1, text: 'Học Svelte', done: false },
{ id: 2, text: 'Xây dựng một ứng dụng', done: true },
{ id: 3, text: 'Ăn mừng 🎉', done: false }
]);
</script>
<ul>
{#each todos as { text, done }}
<li>
{text} {done ? '✅' : '❌'}
</li>
{/each}
</ul>
Phân tích
todos
là một mảng chứa các đối tượng. Mỗi đối tượng cóid
,text
vàdone
.{#each todos as { text, done }}
: Đây là destructuring! Thay vì viếttodo.text
vàtodo.done
, chúng ta unpack chúng thành các biến cục bộ.<li>{text} {done ? '✅' : '❌'}</li>
: Hiển thị mỗi nhiệm vụ với dấu kiểm hoặc dấu chéo.
👉 Kết quả: Một danh sách todo gọn gàng.
Sử dụng Chỉ số
Đôi khi bạn cần biết số thứ tự trong danh sách (0, 1, 2…). Bạn có thể lấy chỉ số đó với , i
.
Ví dụ 3: Danh sách số thứ tự
svelte
<script>
let items = $state(['Đầu tiên', 'Thứ hai', 'Thứ ba']);
</script>
<ol>
{#each items as item, i}
<li>{i + 1}. {item}</li>
{/each}
</ol>
Phân tích
{#each items as item, i}
: Biến thứ hai là chỉ số (bắt đầu từ 0).<li>{i + 1}. {item}</li>
: Thêm số thứ tự trước mỗi mục.
👉 Kết quả:
- Đầu tiên
- Thứ hai
- Thứ ba
Xử lý Danh sách Rỗng
Nếu mảng rỗng thì sao? Theo mặc định, sẽ không có gì được hiển thị. Đôi khi điều đó là ổn, nhưng thường bạn muốn hiển thị một thông báo như “Chưa có mục nào.”
Svelte cung cấp cho chúng ta một {:else}
bên trong {#each}
.
Ví dụ 4: Danh sách trống
svelte
<script>
let books = $state([]);
</script>
<ul>
{#each books as book}
<li>{book}</li>
{:else}
<li>Không tìm thấy sách nào.</li>
{/each}
</ul>
Phân tích
let books = $state([]);
: Bắt đầu với mảng rỗng.{#each books as book} ... {:else} ... {/each}
: Nếu có sách, hiển thị chúng. Ngược lại, hiển thị nhánh{:else}
.
👉 Kết quả: Danh sách sẽ hiển thị “Không tìm thấy sách nào.” cho đến khi bạn thêm thứ gì đó vào mảng.
Vòng lặp Lồng
Vòng lặp có thể lồng vào nhau — một trong những vòng lặp khác. Ví dụ, hãy hiển thị các danh mục cùng với các mục của chúng.
Ví dụ 5: Danh sách danh mục
svelte
<script>
let categories = $state([
{ name: 'Trái cây', items: ['Apple', 'Banana'] },
{ name: 'Rau củ', items: ['Carrot', 'Lettuce'] }
]);
</script>
<div>
{#each categories as category}
<h2>{category.name}</h2>
<ul>
{#each category.items as item}
<li>{item}</li>
{/each}
</ul>
{/each}
</div>
Phân tích
categories
là một mảng chứa các đối tượng. Mỗi đối tượng có mộtname
và một mảngitems
.- Vòng lặp bên ngoài
{#each}
→ lặp qua các danh mục. - Vòng lặp bên trong
{#each}
→ lặp qua các mục trong mỗi danh mục. <h2>
hiển thị tên danh mục,<li>
liệt kê các mục.
👉 Kết quả:
Trái cây
- Apple
- Banana
Rau củ
- Carrot
- Lettuce
⚠️ Cảnh báo: Vòng lặp lồng sâu có thể trở nên khó đọc. Nếu mọi thứ bắt đầu trông rối rắm, hãy xem xét việc chia thành các thành phần nhỏ hơn.
Nhắc nhở về Tính phản ứng
Với $state
, mảng và đối tượng là phản ứng ngay cả khi bạn biến đổi chúng. Điều này có nghĩa là các thao tác như push
, pop
, splice
, và cập nhật thuộc tính đối tượng sẽ kích hoạt giao diện người dùng cập nhật. Không cần thêm thủ thuật nào khác.
Ví dụ 6: Todos Phản ứng
svelte
<script>
let todos = $state(['Học Svelte', 'Xây dựng một ứng dụng']);
let newTodo = $state('');
function addTodo() {
const v = newTodo.trim();
if (!v) return;
// ✅ Điều này hoạt động với $state: biến đổi phản ứng
todos.push(v);
newTodo = '';
}
</script>
<input
placeholder="Todo mới"
bind:value={newTodo}
/>
<button on:click={addTodo}>Thêm</button>
<ul>
{#each todos as todo}
<li>{todo}</li>
{/each}
</ul>
Phân tích
let todos = $state([...])
: Tạo mảng phản ứng.todos.push(v)
: Đủ để cập nhật giao diện người dùng. Bạn sẽ thấy mục mới xuất hiện ngay lập tức.
Thử nghiệm:
- Thêm một vài todo và xem chúng được hiển thị ngay lập tức.
- Thay thế
todos.push(v)
bằng dòng gán lại và xác nhận cả hai cách đều hoạt động giống nhau.
Kiểu dáng Danh sách
Danh sách không nhất thiết phải trông nhàm chán. Bạn có thể sử dụng điều kiện và kiểu dáng bên trong vòng lặp để làm cho mỗi mục phản ánh trạng thái của nó về mặt hình thức.
Ví dụ 7: Todos có kiểu dáng
svelte
<script>
let todos = $state([
{ text: 'Viết mã', done: true },
{ text: 'Kiểm tra ứng dụng', done: false },
{ text: 'Gửi sản phẩm', done: false }
]);
</script>
<ul>
{#each todos as { text, done }}
<li style="color: {done ? 'green' : 'red'}">
{done ? '✅' : '❌'} {text}
</li>
{/each}
</ul>
Phân tích
todos
bây giờ là một mảng chứa các đối tượng.- Chúng ta destructure
{ text, done }
ngay trong vòng lặp để rõ ràng. - Nếu
done
là true → hiển thị ✅ và chuyển văn bản thành màu xanh. - Nếu không → hiển thị ❌ và chuyển văn bản thành màu đỏ.
👉 Danh sách ngay lập tức truyền đạt trạng thái: các nhiệm vụ đã hoàn thành màu xanh, các nhiệm vụ đang chờ màu đỏ.
Lưu ý về Hiệu suất
Bạn có thể lo lắng về hiệu suất khi có hàng ngàn mục. Tin tốt là Svelte biên dịch vòng lặp thành mã rất hiệu quả. Nó chỉ cập nhật những gì thực sự thay đổi.
👉 Đối với hầu hết các ứng dụng: đừng lo lắng.
👉 Đối với danh sách lớn (hàng chục ngàn hàng): bạn có thể xem xét các thư viện virtualization (chỉ hiển thị những gì đang hiển thị). Đó là một tối ưu hóa nâng cao.
Dự án Mini — Giỏ hàng 🛒
Hãy cùng kết hợp tất cả vào một thứ gì đó thực tế hơn: một giỏ hàng.
Ví dụ 8: Giỏ hàng
svelte
<script>
let cart = $state([
{ id: 1, name: 'Apple', qty: 2 },
{ id: 2, name: 'Banana', qty: 1 }
]);
let newItem = $state('');
</script>
<input
placeholder="Mặt hàng mới"
bind:value={newItem}
/>
<button
on:click={() => {
if (newItem.trim()) {
const id = Date.now();
cart = [...cart, { id, name: newItem, qty: 1 }];
newItem = '';
}
}}
>
Thêm
</button>
<ul>
{#each cart as item, i (item.id)}
<li>
{i + 1}. {item.name} (x{item.qty})
<button on:click={() => {
cart = cart.map(it =>
it.id === item.id ? { ...it, qty: it.qty + 1 } : it
);
}}>+</button>
<button on:click={() => {
cart = cart.filter(it => it.id !== item.id);
}}>Xóa</button>
</li>
{:else}
<li>Giỏ hàng trống.</li>
{/each}
</ul>
Phân tích
cart
bắt đầu với hai mục. Mỗi mục cóid
,name
, vàqty
.- Thêm một mục: tạo một đối tượng mới với một
id
mới (sử dụngDate.now()
như một khóa duy nhất đơn giản). - Mỗi dòng hiển thị chỉ số, tên và số lượng của mục.
- Nút
+
tăng số lượng → chúng ta sử dụng.map()
và gán lạicart
. - Nút
Xóa
xóa mục → chúng ta sử dụng.filter()
và gán lạicart
. - Nhánh
{:else}
đảm bảo nếu giỏ hàng rỗng, giao diện sẽ thông báo.
👉 Giỏ hàng nhỏ này kết hợp tất cả các tính năng của vòng lặp: lặp, chỉ số, khóa, phản ứng, và xử lý rỗng.
Kết luận 🎁
Chúng ta đã đi qua nhiều điều trong hành trình này về vòng lặp. Hãy tóm tắt:
{#each}
→ lặp lại một khối cho mỗi mục.- Destructuring → làm cho mã sạch hơn.
- Chỉ số →
, i
cho bạn vị trí. - Danh sách rỗng → thêm
{:else}
để giao diện không bị trống. - Vòng lặp lồng → hữu ích nhưng đừng lạm dụng.
- Tính phản ứng → với
$state
, cả biến đổi và gán lại đều phản ứng; chọn cái nào rõ ràng hơn cho mã của bạn. - Khóa → rất quan trọng cho việc cập nhật ổn định khi mục di chuyển hoặc thay đổi.
- Hiệu suất → Svelte rất hiệu quả, bạn thường không cần lo lắng.
👉 Tóm lại: vòng lặp + điều kiện = giao diện động phản ứng với bất kỳ lượng dữ liệu nào.
Giờ đây, bộ công cụ Svelte của bạn đã bao gồm:
- Props (truyền dữ liệu xuống).
- Events (gửi tin nhắn lên).
- Điều kiện (ra quyết định).
- Vòng lặp (xử lý các bộ sưu tập).
Đó là một nền tảng vững chắc để xây dựng gần như bất cứ điều gì. 🚀
Theo dõi
Hãy theo dõi tôi trên DEV để nhận các bài viết trong chuỗi sâu về Svelte. Nếu bạn thấy bài viết hữu ích, hãy để lại một phản ứng (thích / đánh dấu) — điều đó sẽ giúp tôi có động lực tạo thêm nhiều nội dung hơn nữa.