Tổng hợp kiến thức cả loạt bài để xây dựng ứng dụng quản lý công việc (Todo) bằng TypeScript và Vite: có kiểu dữ liệu chặt, lọc theo trạng thái, lưu vào localStorage và kiểm thử.
- Khởi tạo dự án Vite + TypeScript
- Thiết kế kiểu dữ liệu và tách mô-đun
- Lưu trữ bền vững và đọc lại an toàn
- Kiểm thử logic bằng Vitest
- Vite
- Công cụ dev server và đóng gói front-end rất nhanh.
- State
- Trạng thái dữ liệu hiện tại của ứng dụng.
- Pure function
- Hàm không tác dụng phụ, dễ kiểm thử.
Khái niệm và lý thuyết
Kiến trúc đơn giản mà rõ ràng: tầng kiểu (types.ts), tầng logic thuần (store.ts: thêm, sửa, lọc, không đụng DOM), tầng lưu trữ (storage.ts) và tầng giao diện (main.ts). Tách như vậy giúp kiểm thử logic mà không cần trình duyệt, và đổi giao diện (sang React chẳng hạn) mà không viết lại logic.
Khởi tạo
npm create vite@latest todo-ts -- --template vanilla-ts
cd todo-ts
npm install
npm install -D vitest
npm run devtypes.ts và store.ts
// types.ts
export type Loc = "tat-ca" | "dang-lam" | "xong";
export interface CongViec { id: string; tieuDe: string; xong: boolean; taoLuc: number }
// store.ts (hàm thuần)
import type { CongViec, Loc } from "./types";
export const them = (ds: CongViec[], tieuDe: string): CongViec[] =>
[...ds, { id: crypto.randomUUID(), tieuDe, xong: false, taoLuc: Date.now() }];
export const dao = (ds: CongViec[], id: string) =>
ds.map(c => c.id === id ? { ...c, xong: !c.xong } : c);
export const loc = (ds: CongViec[], l: Loc) =>
l === "tat-ca" ? ds : ds.filter(c => (l === "xong") === c.xong);storage.ts
import type { CongViec } from "./types";
const KHOA = "todo-ts";
export function doc(): CongViec[] {
try {
const raw: unknown = JSON.parse(localStorage.getItem(KHOA) ?? "[]");
return Array.isArray(raw) ? raw.filter(x => typeof x?.tieuDe === "string") : [];
} catch { return []; }
}
export const ghi = (ds: CongViec[]) => localStorage.setItem(KHOA, JSON.stringify(ds));main.ts
import { them, dao, loc } from "./store";
import { doc, ghi } from "./storage";
import type { Loc } from "./types";
let ds = doc(); let boLoc: Loc = "tat-ca";
const form = document.querySelector<HTMLFormElement>("#form")!;
const input = document.querySelector<HTMLInputElement>("#tieu-de")!;
const ul = document.querySelector<HTMLUListElement>("#ds")!;
function ve() {
ul.innerHTML = "";
for (const c of loc(ds, boLoc)) {
const li = document.createElement("li");
li.textContent = c.tieuDe; li.className = c.xong ? "xong" : "";
li.onclick = () => { ds = dao(ds, c.id); ghi(ds); ve(); };
ul.append(li);
}
}
form.onsubmit = e => { e.preventDefault();
if (input.value.trim()) { ds = them(ds, input.value.trim()); ghi(ds); input.value = ""; ve(); } };
ve();Kiểm thử store.ts
// store.test.ts
import { describe, it, expect } from "vitest";
import { them, dao, loc } from "./store";
describe("store", () => {
it("thêm và đảo trạng thái", () => {
let ds = them([], "Học TS");
ds = dao(ds, ds[0].id);
expect(loc(ds, "xong")).toHaveLength(1);
});
});Chạy npx vitest. Kiểm tra kiểu toàn dự án bằng npx tsc --noEmit.
Đưa dự án lên GitHub và bật GitHub Pages hoặc Vercel để có bản chạy trực tuyến, đưa vào hồ sơ năng lực.
Dấu ! ở main.ts chấp nhận được vì các phần tử nằm sẵn trong index.html; với phần tử tạo động, phải kiểm tra null.
- Thêm chức năng sửa tiêu đề khi nhấp đúp.
- Thêm hạn chót (Date) và sắp xếp theo hạn.
- Thêm nút xóa các việc đã xong, kèm kiểm thử.
- Mở rộng: chuyển giao diện sang React + TypeScript, giữ nguyên store.ts.
- Tách kiểu, logic, lưu trữ và giao diện.
- Hàm thuần giúp kiểm thử dễ.
- Dữ liệu đọc từ localStorage vẫn cần kiểm tra.
- Vite + Vitest + tsc là bộ công cụ chuẩn cho dự án TS front-end.