0
0
Lập trình
Sơn Tùng Lê
Sơn Tùng Lê103931498422911686980

Vòng lặp trong Svelte: Tạo Todo App với {#each}

Đăng vào 4 ngày trước

• 9 phút đọc

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:

  1. Lặp lại các phần tử với {#each}.
  2. Lấy các thuộc tính dễ dàng bằng cách sử dụng destructuring.
  3. Sử dụng chỉ số khi cần “đầu tiên, thứ hai, thứ ba.”
  4. Xử lý các mảng rỗng một cách tinh tế.
  5. 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 Copy
<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 Copy
<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, textdone.
  • {#each todos as { text, done }}: Đây là destructuring! Thay vì viết todo.texttodo.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 Copy
<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ả:

  1. Đầu tiên
  2. Thứ hai
  3. 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 Copy
<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 Copy
<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ột name và một mảng items.
  • 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 Copy
<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 Copy
<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 Copy
<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ụng Date.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ại cart.
  • Nút Xóa xóa mục → chúng ta sử dụng .filter() và gán lại cart.
  • 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.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào