# ✅ Checklist Migration: React → Svelte 5 **Phiên bản:** Svelte 5 (Runes API) | **Cập nhật:** 2025 --- ## 📋 Mục lục 1. [Chuẩn bị](#1-chuẩn-bị) 2. [Mapping React Hooks → Svelte Runes](#2-mapping-react-hooks--svelte-runes) 3. [Kiểm tra từng Component](#3-kiểm-tra-từng-component) 4. [Common Pitfalls](#4-common-pitfalls) --- ## 1. Chuẩn bị ### 1.1 Đọc tài liệu bắt buộc - [ ] Đọc [Svelte 5 Runes docs](https://svelte.dev/docs/svelte/what-are-runes) — hiểu khái niệm compiler-based reactivity - [ ] Đọc [Migration guide](https://svelte.dev/docs/svelte/v5-migration-guide) — nắm breaking changes từ Svelte 4 - [ ] Đọc [SvelteKit docs](https://svelte.dev/docs/kit/introduction) — nếu project dùng routing/SSR - [ ] Xem qua [Svelte 5 Playground](https://svelte.dev/playground) — thử các runes cơ bản trước khi code thật ### 1.2 Cài đặt môi trường - [ ] Node.js >= 18.x đã được cài - [ ] Khởi tạo project SvelteKit mới ```bash npx sv create my-app # Chọn: SvelteKit minimal → TypeScript → Svelte 5 ``` - [ ] Hoặc thêm Svelte 5 vào project Vite hiện có ```bash npm install svelte@^5 @sveltejs/vite-plugin-svelte@^4 ``` - [ ] Kiểm tra `svelte.config.js` đã đúng ```js // svelte.config.js import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; export default { preprocess: vitePreprocess(), }; ``` - [ ] Kiểm tra `vite.config.js` / `vite.config.ts` ```ts import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [sveltekit()], }); ``` ### 1.3 Setup công cụ hỗ trợ - [ ] Cài VS Code extension **Svelte for VS Code** (`svelte.svelte-vscode`) - [ ] Cài ESLint + Prettier cho Svelte ```bash npm install -D eslint-plugin-svelte prettier-plugin-svelte ``` - [ ] Cấu hình TypeScript — thêm vào `tsconfig.json` ```json { "compilerOptions": { "moduleResolution": "bundler", "verbatimModuleSyntax": true } } ``` - [ ] Cài Vitest để chạy unit test tương đương Jest/RTL ```bash npm install -D vitest @testing-library/svelte ``` ### 1.4 Lập kế hoạch migrate - [ ] Liệt kê toàn bộ components cần convert (dùng `find . -name "*.tsx" -o -name "*.jsx"`) - [ ] Phân loại theo độ phức tạp: **Simple** (chỉ state/props) → **Medium** (hooks, context) → **Complex** (HOC, render props) - [ ] Ưu tiên migrate từ **leaf components** (không có children) lên dần - [ ] Giữ React và Svelte chạy song song trong giai đoạn chuyển đổi nếu project lớn --- ## 2. Mapping React Hooks → Svelte Runes > 💡 **Nguyên tắc chung:** Runes là **compiler directives** — chúng chỉ hoạt động trong `.svelte` file hoặc `.svelte.ts/.svelte.js` file. Không phải runtime functions như React hooks. --- ### 2.1 `useState` → `$state` | React | Svelte 5 | |-------|----------| | `const [count, setCount] = useState(0)` | `let count = $state(0)` | | `setCount(5)` | `count = 5` | | `setCount(prev => prev + 1)` | `count += 1` | | `const [obj, setObj] = useState({a: 1})` | `let obj = $state({a: 1})` | | `setObj({...obj, a: 2})` | `obj.a = 2` *(deep reactive)* | **React:** ```tsx // React function Counter() { const [count, setCount] = useState(0); const [user, setUser] = useState({ name: 'An', age: 25 }); return (
); } ``` **Svelte 5:** ```svelte ``` > ⚠️ **Khác biệt quan trọng:** `$state` object hỗ trợ **deep reactivity** — mutate trực tiếp property là OK. Không cần `setState` hay spread operator. --- ### 2.2 `useMemo` → `$derived` | React | Svelte 5 | |-------|----------| | `useMemo(() => a + b, [a, b])` | `$derived(a + b)` | | `useMemo(() => list.filter(...), [list])` | `$derived(list.filter(...))` | | Logic phức tạp nhiều bước | `$derived.by(() => { ... })` | | Dependency array thủ công `[a, b]` | Tự động track — không cần khai báo | **React:** ```tsx // React function ProductList({ products, category }) { const filtered = useMemo( () => products.filter(p => p.category === category), [products, category] ); const total = useMemo( () => filtered.reduce((sum, p) => sum + p.price, 0), [filtered] ); const summary = useMemo(() => { const avg = total / filtered.length; const max = Math.max(...filtered.map(p => p.price)); return { avg, max, count: filtered.length }; }, [filtered, total]); return
{summary.count} sản phẩm, TB: {summary.avg}
; } ``` **Svelte 5:** ```svelte
{summary.count} sản phẩm, TB: {summary.avg}
``` > ✅ **Lợi thế:** Svelte tự động phát hiện dependencies. Không bao giờ bị lỗi "missing dependency" như eslint-plugin-react-hooks. --- ### 2.3 `useEffect` → `$effect` | React | Svelte 5 | |-------|----------| | `useEffect(() => {}, [])` — run once | Không có tương đương trực tiếp — dùng `onMount` | | `useEffect(() => {}, [dep])` — run on change | `$effect(() => { /* đọc dep ở đây */ })` | | `useEffect(() => { return cleanup }, [dep])` | `$effect(() => { return () => cleanup() })` | | `useEffect(() => {})` — run every render | `$effect(...)` — tương đương nhưng chỉ khi deps thay đổi | **React:** ```tsx // React function SearchBox({ query }) { const [results, setResults] = useState([]); // Run once on mount useEffect(() => { console.log('Component mounted'); return () => console.log('Component unmounted'); }, []); // Run when query changes useEffect(() => { if (!query) return; const controller = new AbortController(); fetch(`/api/search?q=${query}`, { signal: controller.signal }) .then(r => r.json()) .then(setResults) .catch(err => { if (err.name !== 'AbortError') console.error(err); }); // Cleanup: hủy request cũ khi query đổi return () => controller.abort(); }, [query]); return ; } ``` **Svelte 5:** ```svelte ``` > ⚠️ **Quan trọng:** `$effect` **KHÔNG chạy trong SSR** (server-side rendering). Dùng `$effect.pre()` nếu cần chạy trước khi DOM update. --- ### 2.4 `useRef` → `$state` hoặc `bind:this` | React | Svelte 5 | |-------|----------| | `useRef(null)` → DOM element | `let el = $state()` + `bind:this={el}` | | `useRef(value)` → mutable, không trigger re-render | `let val = $state.raw(value)` | | `ref.current` | `el` trực tiếp | **React:** ```tsx // React function VideoPlayer() { const videoRef = useRef(null); const playCountRef = useRef(0); // Không trigger re-render const play = () => { videoRef.current?.play(); playCountRef.current += 1; console.log('Played:', playCountRef.current, 'times'); }; return