Svelte Stores - Quản lý State trong Svelte
Svelte Stores - Quản lý State
Section titled “Svelte Stores - Quản lý State”Giới thiệu
Section titled “Giới thiệu”Svelte Stores là một cách đơn giản và mạnh mẽ để quản lý state (trạng thái) trong ứng dụng Svelte. Khác với Redux hay Context API trong React, Svelte Stores được tích hợp sẵn và cực kỳ dễ sử dụng.
Tạo Store
Section titled “Tạo Store”Writable Store
Section titled “Writable Store”import { writable } from 'svelte/store';
export const count = writable(0);Readable Store (chỉ đọc)
Section titled “Readable Store (chỉ đọc)”import { readable } from 'svelte/store';
const initialUser = { name: 'Guest', email: '' };
export const user = readable(initialUser);Derived Store (tính toán từ store khác)
Section titled “Derived Store (tính toán từ store khác)”import { writable, derived } from 'svelte/store';
export const products = writable([ { id: 1, name: 'Sản phẩm A', price: 100000 }, { id: 2, name: 'Sản phẩm B', price: 200000 },]);
// Tự động tính tổngexport const totalPrice = derived(products, ($products) => $products.reduce((sum, p) => sum + p.price, 0));Sử dụng trong Component
Section titled “Sử dụng trong Component”Cách 1: Subscribe trực tiếp
Section titled “Cách 1: Subscribe trực tiếp”<script> import { count } from '../stores/counter.js';
// Tự động unsubscribe khi component bị hủy const unsubscribe = count.subscribe(value => { console.log('Count:', value); });</script>
<h1>Count: {$count}</h1><button on:click={() => $count++}>Tăng</button>Cách 2: Dùng $ prefix (khuyên dùng)
Section titled “Cách 2: Dùng $ prefix (khuyên dùng)”<script> import { count } from '../stores/counter.js';</script>
<!-- $count tự động subscribe/unsubscribe --><h1>Count: {$count}</h1>
<button on:click={() => $count++}> Tăng (+1)</button>
<button on:click={() => $count--}> Giảm (-1)</button>
<button on:click={() => $count = 0}> Reset</button>Store Methods
Section titled “Store Methods”import { writable } from 'svelte/store';
const count = writable(0);
count.set(10); // Đặt giá trị = 10update()
Section titled “update()”const count = writable(0);
count.update(n => n + 1); // Tăng lên 1
// Hoặc với logic phức tạpcount.update(n => { if (n >= 10) return 0; return n + 1;});subscribe()
Section titled “subscribe()”const count = writable(0);
const unsubscribe = count.subscribe(value => { console.log(value);});
unsubscribe(); // Hủy subscriptionVí dụ thực tế: Giỏ hàng
Section titled “Ví dụ thực tế: Giỏ hàng”Tạo Cart Store
Section titled “Tạo Cart Store”import { writable, derived } from 'svelte/store';
function createCart() { const { subscribe, set, update } = writable([]);
return { subscribe, addItem: (product) => update(items => { const existing = items.find(i => i.id === product.id); if (existing) { return items.map(i => i.id === product.id ? { ...i, quantity: i.quantity + 1 } : i ); } return [...items, { ...product, quantity: 1 }]; }), removeItem: (id) => update(items => items.filter(i => i.id !== id) ), clear: () => set([]), updateQuantity: (id, quantity) => update(items => items.map(i => i.id === id ? { ...i, quantity } : i ) ) };}
export const cart = createCart();
// Derived: Tổng tiềnexport const cartTotal = derived(cart, ($cart) => $cart.reduce((sum, item) => sum + item.price * item.quantity, 0));
// Derived: Số lượng itemsexport const cartCount = derived(cart, ($cart) => $cart.reduce((sum, item) => sum + item.quantity, 0));Sử dụng trong Product Card
Section titled “Sử dụng trong Product Card”<script> import { cart } from '../stores/cart.js';
export let product;
function addToCart() { cart.addItem(product); }</script>
<div class="product-card"> <h3>{product.name}</h3> <p>{product.price.toLocaleString()}đ</p> <button on:click={addToCart}>Thêm vào giỏ</button></div>Sử dụng trong Cart Icon
Section titled “Sử dụng trong Cart Icon”<script> import { cartCount, cartTotal } from '../stores/cart.js';</script>
<a href="/cart" class="cart-icon"> 🛒 Giỏ hàng {#if $cartCount > 0} <span class="badge">{$cartCount}</span> {/if} <span class="total">{$cartTotal.toLocaleString()}đ</span></a>
<style> .cart-icon { position: relative; display: inline-flex; align-items: center; gap: 0.5rem; }
.badge { background: red; color: white; border-radius: 50%; padding: 0.2rem 0.5rem; font-size: 0.75rem; }</style>Stores trong Astro + Svelte
Section titled “Stores trong Astro + Svelte”Khi dùng Svelte trong Astro, bạn cần lưu ý:
---// ⚠️ Store chỉ hoạt động ở client-side// Không dùng được cho SSR data fetching---
<script> import { count } from '../stores/counter.js';</script>
<!-- ✅ Hoạt động: Interactive component --><Counter client:load />
<!-- ⚠️ Lưu ý: Mỗi island là một instance riêng biệt --><!-- State không được chia sẻ giữa các islands -->Giải pháp: Dùng localStorage
Section titled “Giải pháp: Dùng localStorage”import { writable } from 'svelte/store';
function createPersistentStore(key, startValue) { const stored = localStorage.getItem(key); const initial = stored ? JSON.parse(stored) : startValue; const { subscribe, set, update } = writable(initial);
return { subscribe, set: (value) => { localStorage.setItem(key, JSON.stringify(value)); set(value); }, update: (fn) => { update(value => { const newValue = fn(value); localStorage.setItem(key, JSON.stringify(newValue)); return newValue; }); } };}
export const theme = createPersistentStore('theme', 'light');export const userPrefs = createPersistentStore('prefs', {});Best Practices
Section titled “Best Practices”-
Tạo file store riêng - Đặt trong
src/stores/ -
Dùng $ prefix - Svelte tự động subscribe/unsubscribe
-
Tránh global state không cần thiết - Chỉ dùng store khi cần chia sẻ state
-
Dùng derived stores - Tính toán từ store khác thay vì trong component
-
Persistent stores - Với localStorage cho settings, theme
Kết luận
Section titled “Kết luận”Svelte Stores là một trong những điểm mạnh nhất của Svelte:
- ✅ Đơn giản - Không boilerplate
- ✅ Hiệu năng cao - Tự động subscription management
- ✅ TypeScript friendly - Dễ dàng typing
- ✅ Devtools hỗ trợ - Svelte DevTools hiển thị stores
Hãy sử dụng stores cho state cần chia sẻ giữa nhiều components!