Làm chủ Svelte 5 Runes: Xây dựng ứng dụng Web Reactive với cú pháp hiện đại

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

Sự thay đổi về tư duy: Từ phép màu của Trình biên dịch đến Runes

Trong một thời gian dài, Svelte nổi bật nhờ sự đơn giản. Chúng ta chỉ cần dùng let count = 0; và trình biên dịch sẽ xử lý phần còn lại. Tuy nhiên, khi ứng dụng lớn dần, việc quản lý state giữa các file bằng Writable store trở thành một “cơn ác mộng” với quá nhiều mã lặp (boilerplate). Tôi thường thấy mình phải vật lộn với tiền tố $ cho các subscription và lo lắng về rò rỉ bộ nhớ (memory leak) mỗi khi quên hủy đăng ký trong các file không phải component.

Svelte 5 thay đổi cuộc chơi với Runes. Thay vì dựa vào việc khai báo biến ở cấp cao nhất vốn chỉ hoạt động trong các file .svelte, Runes cung cấp một hệ thống reactive dựa trên signal hoạt động ở mọi nơi.

Điều này đưa Svelte đến gần hơn với cách các framework hiện đại như Solid hay Vue xử lý tính reactive, nhưng với cú pháp gọn gàng hơn nhiều. Tôi đã áp dụng phương pháp này vào dự án thực tế và kết quả luôn ổn định, đặc biệt là khi xử lý các cấu trúc dữ liệu phức tạp cần được chia sẻ qua nhiều module.

Cách truyền thống và Cách tiếp cận Runes

Trong Svelte 4, tính reactive bị giới hạn trong phạm vi component. Nếu bạn muốn tính toán một giá trị, bạn sử dụng nhãn $:. Nếu bạn muốn chia sẻ state, bạn phải dùng tới svelte/store. Dưới đây là bảng so sánh nhanh về cách viết code hiện tại:

// Svelte 4 - Chỉ dành cho Component
let count = 0;
$: doubled = count * 2;

// Svelte 5 - Hoạt động ở mọi nơi
let count = $state(0);
let doubled = $derived(count * 2);

Sự thay đổi lớn nhất là $state$derived giờ đây đã trở nên rõ ràng. Chúng ta không còn dựa vào việc trình biên dịch tự dự đoán biến nào là reactive. Sự minh bạch này giúp việc debug dễ dàng hơn đáng kể vì bạn có thể truy xuất chính xác nguồn gốc của một giá trị reactive.

Ưu và nhược điểm của phương pháp Runes

Chuyển sang một cú pháp mới luôn đi kèm với những sự đánh đổi. Sau khi tái cấu trúc (refactor) một số công cụ nội bộ sang Svelte 5, tôi đã xác định được những điểm mạnh và những rào cản tiềm ẩn.

Ưu điểm

  • Tính Reactive phổ quát: Bạn có thể định nghĩa reactive state trong các file .js hoặc .ts. Bạn không còn bị buộc phải giữ logic bên trong các khối component chỉ để duy trì tính reactive.
  • Không còn Auto-subscription: Cú pháp $store đã biến mất. Bạn chỉ cần truy cập biến trực tiếp. Điều này giúp giảm bớt gánh nặng tư duy khi đọc code.
  • Cập nhật chi tiết (Fine-grained): Svelte 5 sử dụng signal bên dưới, nghĩa là chỉ phần cụ thể của DOM thay đổi mới được cập nhật, thay vì chạy lại các phần logic lớn của component.
  • Lifecycle thống nhất: $effect thay thế onMount, afterUpdateonDestroy bằng một cơ chế duy nhất và dễ dự đoán hơn.

Thách thức

  • Thay đổi mang tính phá vỡ (Breaking Changes): Nếu bạn có một codebase Svelte 4 lớn, việc chuyển đổi không chỉ đơn thuần là tìm và thay thế. Bạn cần tư duy lại cách luồng dữ liệu vận hành.
  • Dài dòng hơn: Việc viết $state(0) tốn nhiều thao tác gõ phím hơn một chút so với let count = 0. Một số nhà phát triển có thể thấy các rune rõ ràng này hơi “rối mắt” lúc mới bắt đầu.

Cấu hình đề xuất cho Svelte 5

Nếu bạn muốn bắt đầu thử nghiệm với Runes, tôi khuyên bạn nên thiết lập một dự án Vite mới. Vì Svelte 5 là hướng đi hiện tại của hệ sinh thái, các công cụ hỗ trợ đã khá hoàn thiện.

# Tạo một dự án Svelte mới
npm create vite@latest my-svelte-app -- --template svelte-ts

# Di chuyển vào thư mục
cd my-svelte-app

# Cài đặt các dependency
npm install

# Đảm bảo bạn đang sử dụng phiên bản Svelte mới nhất
npm install svelte@next

Trong file svelte.config.js, bạn không cần bất kỳ cấu hình đặc biệt nào để kích hoạt Runes; chúng đã là một phần của thư viện cốt lõi. Tuy nhiên, tôi luôn khuyên bạn nên bật TypeScript để tận dụng tối đa tính an toàn kiểu (type-safety) mà Runes cung cấp, đặc biệt là khi định nghĩa $props().

Hướng dẫn triển khai: Xây dựng ứng dụng Reactive

Hãy cùng tìm hiểu cách triển khai một hệ thống reactive cơ bản bằng bốn Rune cốt lõi: $state, $derived, $props, và $effect.

1. Quản lý State với $state

Thay vì gán giá trị đơn thuần, hãy sử dụng $state cho bất kỳ dữ liệu nào cần kích hoạt cập nhật giao diện (UI). Rune này hoạt động cho cả kiểu dữ liệu nguyên thủy (primitive), mảng và đối tượng.

<script>
  let user = $state({
    name: 'Kỹ sư',
    tasks: []
  });

  function addTask(task) {
    user.tasks.push(task); // Svelte 5 tự động theo dõi điều này!
  }
</script>

2. Giá trị phái sinh với $derived

Bất cứ khi nào bạn cần một giá trị phụ thuộc vào một phần state khác, $derived là lựa chọn hàng đầu. Nó thay thế các khai báo reactive $: cũ. Tôi thấy cách này gọn gàng hơn nhiều vì nó hoạt động như một biến bình thường nhưng luôn được đồng bộ.

<script>
  let items = $state([10, 20, 30]);
  let total = $derived(items.reduce((a, b) => a + b, 0));
</script>

<p>Tổng là: {total}</p>

3. Truyền dữ liệu giữa các Component với $props

Việc truyền dữ liệu vào các component trước đây thường liên quan đến export let name;. Trong Svelte 5, chúng ta sử dụng $props(). Điều này mang lại cảm giác giống như phép phân rã (destructuring) trong JavaScript hiện đại và cho phép xử lý các giá trị mặc định tốt hơn.

<script>
  let { title, status = 'đang xử lý' } = $props();
</script>

<h1>{title}</h1>
<span>Trạng thái: {status}</span>

4. Xử lý Side Effect với $effect

Đây là phần thú vị nhất. $effect thay thế hầu hết các lifecycle hook. Nó chạy bất cứ khi nào các giá trị reactive bên trong nó thay đổi. Nó rất hữu ích cho việc ghi log, đồng bộ với local storage, hoặc gọi API khi ID thay đổi.

<script>
  let userId = $state(1);

  $effect(() => {
    console.log(`User ID đã thay đổi thành: ${userId}`);
    
    // Logic dọn dẹp (thay thế onDestroy)
    return () => {
      console.log('Dọn dẹp trước lần chạy kế tiếp hoặc khi hủy component');
    };
  });
</script>

5. Thay thế Store bằng State chia sẻ

Một “tuyệt chiêu” thực sự trong Svelte 5 là tạo một file state chia sẻ mà không cần sử dụng svelte/store. Bạn chỉ đơn giản là export một đối tượng chứa các giá trị $state. Tôi nhận thấy đây là cách ổn định nhất để quản lý state toàn cục trong các dự án thực tế gần đây.

// logger.js (hoặc .ts)
export const appState = $state({
  theme: 'dark',
  toggleTheme() {
    this.theme = this.theme === 'dark' ? 'light' : 'dark';
  }
});

Sau đó, trong bất kỳ component nào, bạn chỉ cần import appState và sử dụng trực tiếp. Không cần tiền tố $, không cần đăng ký thủ công và không có mã lặp.

Lời kết

Runes đại diện cho một bước tiến hóa quan trọng của Svelte. Mặc dù việc chuyển đổi từ cách tiếp cận dựa nặng vào trình biên dịch sang một hệ thống reactive rõ ràng có thể mang lại cảm giác thay đổi lớn, nhưng lợi ích về khả năng tái sử dụng code và sự minh bạch là không thể phủ nhận. Đội ngũ của tôi đã nhận thấy sự sụt giảm các “lỗi reactive”—những khoảnh khắc kỳ lạ khi một biến không cập nhật do trình biên dịch không nhận diện được sự phụ thuộc—ngay lập tức sau khi chuyển sang Runes.

Nếu bạn đang bắt đầu một dự án mới hôm nay, tôi thực sự khuyên bạn nên sử dụng Svelte 5 ngay từ đầu. Nó giúp trải nghiệm phát triển trở nên dễ dự đoán hơn nhiều và đưa Svelte đi đúng hướng với tương lai của hệ sinh thái web.

Share: