# Sáu bước dựng demo React cho khách, bắt đầu từ máy chưa có Node

> Màn hình chạy được trên localhost mới là nửa đường; nửa còn lại là kiểm kiểu, build sạch và không để lộ một chiếc API key nào trong bundle.

Bản gốc: https://fdetimes.net/vi/bach-khoa/thuc-hanh-dung-giao-dien-noi-bo-bang-react/

Thứ Ba, khách nói: "Thứ Sáu cho bên tôi bấm thử được không?". Backend trả lời câu hỏi trên tài liệu nội bộ đã chạy, nhưng khách không đọc JSON. Họ cần một ô nhập, một nút bấm và một câu trả lời hiện ra trên màn hình. Máy làm việc khách cấp cho bạn thì trống trơn, chưa có cả Node.

Đây là tình huống FDE gặp liên tục, và kỹ năng ở đây không nằm ở React nâng cao. Nó nằm ở chỗ đi từ con số không đến một bản build sạch trong vài giờ, theo cách mà tuần sau đồng nghiệp tiếp quản được. Sáu bước dưới đây theo trọn một ví dụ như vậy, từ lệnh đầu tiên đến thư mục `dist`.

## Vì sao chọn Vite thay vì framework?

Tài liệu chính thức của React khuyến nghị bắt đầu app mới bằng một framework. Họ cũng nói thẳng cái giá của việc tự dựng: linh hoạt hơn, nhưng bạn phải tự chọn công cụ cho routing, data fetching và các mẫu dùng chung khác.

Với một demo một màn hình, cái giá đó gần như bằng không, vì bạn chưa cần routing hay chiến lược fetch phức tạp. Vite cho bạn một khung nhẹ, khởi động nhanh. Nếu demo lớn thành sản phẩm nhiều trang, đó là lúc nên quay lại câu hỏi framework.

## Bước 1: cài đúng Node trước khi gõ lệnh nào khác

Vite yêu cầu Node.js 20.19+ hoặc 22.12+. Trên máy trống, hãy cài bản 22.12 trở lên, qua trình cài đặt chính thức hoặc một trình quản lý phiên bản như nvm nếu máy khách cho phép. Rồi kiểm tra:

```bash
node -v
npm -v
```

Nếu `node -v` in ra số nhỏ hơn mức yêu cầu, dừng lại sửa ngay. Lỗi phiên bản Node hiện ra ở bước sau thường trông như lỗi của Vite, và bạn sẽ mất cả buổi chiều đi nhầm hướng.

## Bước 2: một lệnh dựng khung, một lệnh chạy

Vite dựng khung bằng `npm create vite@latest`. Template React có TypeScript là `react-ts` (ngoài ra còn `react-compiler-ts`):

```bash
npm create vite@latest demo-khach -- --template react-ts
cd demo-khach
npm install
npm run dev
```

Script `dev` mặc định chỉ là `vite`, mở dev server tại `localhost:5173`. Thấy trang mẫu hiện lên là xong phần hạ tầng. Nếu khách đã có sẵn một dự án React viết bằng JavaScript, thay vì dựng mới bạn cài thêm type definitions bằng `npm install --save-dev @types/react @types/react-dom`.

## Bước 3: viết kiểu dữ liệu trước khi viết giao diện

Trước khi đụng vào màn hình, tạo `src/types.ts`. File này là hợp đồng giữa backend và giao diện:

```ts
export type Status = "idle" | "loading" | "done" | "error";

export type Answer = {
text: string;
sources: string[];
};
```

Tài liệu React nói kiểu khai báo cho props vừa dùng để kiểm tra đúng sai, vừa thành tài liệu hiện ngay trong editor. Với FDE, giá trị thứ hai lớn không kém: người tiếp quản mở file là biết API trả về gì mà không phải gọi thử.

## Bước 4: gom mọi lời gọi API vào một chỗ

Tạo `src/api.ts`. Mọi thứ liên quan đến mạng nằm ở đây, để thứ Năm khách đổi địa chỉ backend thì bạn chỉ sửa một dòng:

```ts
import type { Answer } from "./types";

// Chỉ là địa chỉ backend, không phải bí mật
const API_URL = import.meta.env.VITE_API_URL;

export async function readAnswer(question: string) {
const res = await fetch(`${API_URL}/answer`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ question }),
});
if (!res.ok) throw new Error(`API trả về ${res.status}`);
return (await res.json()) as Answer;
}
```

Và file `.env` ở gốc dự án:

```bash
VITE_API_URL=http://localhost:8000
```

Chi tiết sống còn: biến `VITE_` bị đóng gói thẳng vào mã chạy trên trình duyệt, và tài liệu Vite ghi rõ chúng không được chứa thông tin nhạy cảm như API key. Key của mô hình hay của hệ thống khách phải nằm ở backend; giao diện chỉ biết địa chỉ.

**Điểm mấu chốt:** Thứ gì đã nằm trong mã giao diện thì ai mở trình duyệt cũng đọc được.

## Bước 5: nối API vào màn hình

Giờ mới đến `src/App.tsx`, file template đã tạo sẵn. File chứa JSX bắt buộc dùng đuôi `.tsx`, nên đừng đổi tên nó thành `.ts`. Thay toàn bộ nội dung bằng đoạn sau:

```tsx
import { useState } from "react";
import type { FormEvent } from "react";
import { readAnswer } from "./api";
import type { Answer, Status } from "./types";

type UiState = { status: Status; answer: Answer | null };
const initial: UiState = { status: "idle", answer: null };

export default function App() {
const [ui, setUi] = useState(initial);

async function handleSubmit(e: FormEvent) {
e.preventDefault();
const form = new FormData(e.currentTarget as HTMLFormElement);
const question = String(form.get("question") ?? "");
setUi({ status: "loading", answer: null });
try {
const answer = await readAnswer(question);
setUi({ status: "done", answer });
} catch {
setUi({ status: "error", answer: null });
}
}

return (

Hỏi
{ui.status === "error" && Không gọi được API, thử lại.}
{ui.answer && {ui.answer.text}}

);
}
```

Để ý dòng `useState(initial)`. Hook này lấy giá trị khởi tạo để suy ra kiểu. Viết `useState("idle")` trơn thì TypeScript hiểu kiểu là `string`, và lỗi gõ nhầm `"loadng"` lọt qua; viết `useState(null)` thì kiểu là `null`, và bạn không gán được câu trả lời vào.

Cách gọn nhất là đưa cho hook một giá trị khởi tạo đã có kiểu: `initial` được khai báo là `UiState`, nên `status` chỉ nhận đúng bốn giá trị trong union và `answer` nhận được cả `null` lẫn `Answer`. Nếu muốn tách thành nhiều state riêng, cách chuẩn là truyền thẳng `Status` làm tham số kiểu cho `useState`.

Gom hai trạng thái vào một object còn có lợi thêm: không bao giờ xảy ra cảnh status là "error" mà vẫn hiện câu trả lời cũ. Lưu file, quay lại `localhost:5173`, gõ một câu hỏi và bấm. Nếu backend đang chạy, câu trả lời hiện ra; tắt backend đi, dòng báo lỗi hiện ra. Khách sẽ thử cả hai trường hợp, nên bạn cũng phải thử cả hai.

## Bước 6: dev server chạy được chưa có nghĩa là code đúng

Đây là cái bẫy lớn nhất. Vite chỉ transpile file TypeScript, không kiểm tra kiểu. Dev server vui vẻ phục vụ một file sai kiểu, và bạn chỉ phát hiện khi khách bấm đúng nhánh lỗi. Vì thế tài liệu Vite khuyên chạy `tsc --noEmit` khi build bản production:

```bash
npx tsc --noEmit
npm run build
```

Đừng tin lệnh kiểm kiểu chỉ vì nó báo sạch. Hãy tự chứng minh: gõ `"loadng"` vào một chỗ gán status rồi chạy lại. Nếu lệnh vẫn im lặng, nhiều khả năng nó không đọc tới code giao diện; dự án có nhiều file `tsconfig` thì trỏ thẳng vào file chứa code `src` bằng cờ `-p`, cho đến khi lỗi cố tình kia hiện ra.

Sau đó mở `package.json`, xem script `build` có chạy kiểm kiểu trước `vite build` hay không; nếu chưa, thêm vào để không ai trong nhóm quên được. Kết quả build mặc định nằm trong thư mục `dist`, một bộ file tĩnh bạn đặt lên máy chủ web để khách mở bằng trình duyệt.

## Những lỗi khiến buổi demo thứ Sáu đổ vỡ

Lỗi phổ biến nhất là coi dev server là bằng chứng. Màn hình chạy trên localhost chỉ chứng minh trình duyệt dịch được code, không chứng minh code đúng kiểu. Lỗi kế bên là tin một lệnh kiểm kiểu báo sạch mà chưa từng thấy nó bắt được lỗi nào.

Lỗi nguy hiểm nhất là đặt key vào biến `VITE_` "cho nhanh, demo thôi". Bundle demo thường được chuyển tiếp qua email, qua nhóm chat, và key đi theo nó. Một lần như vậy đủ làm mất niềm tin của đội bảo mật phía khách.

Hai lỗi nhỏ hơn nhưng tốn giờ: viết JSX trong file `.ts`, và để `useState` tự suy kiểu từ một chuỗi hay `null` trơn cho state có nhiều trạng thái. Cả hai đều được TypeScript bắt nếu bạn cho nó cơ hội.

## Kỹ năng này nằm ở đâu trong CV của bạn?

Khi đọc job description FDE, hãy để ý những cụm như "build prototypes", "customer-facing demos" hay "rapid iteration". Nếu chúng xuất hiện, sáu bước này chính là thứ bạn nên chứng minh được, và chứng minh bằng kết quả sẽ thuyết phục hơn liệt kê công nghệ.

Trong CV, thay vì ghi "thành thạo React, TypeScript", hãy viết một dòng có kết quả: dựng demo hỏi đáp tài liệu từ máy trống đến bản build trong một buổi, tách lớp API để đổi backend không cần sửa giao diện, key giữ ở server. Một repo công khai có đúng cấu trúc `types.ts`, `api.ts`, `App.tsx` nói thay bạn nhiều hơn mọi tính từ.

Lần tới khách hỏi "thứ Sáu bấm thử được không?", câu trả lời đúng không phải là "được". Câu trả lời đúng là một đường link trỏ vào thư mục `dist`, gửi đi từ chiều thứ Năm.

**Thử ngay tuần này:**

- Trên một máy (hoặc user) chưa có Node, bấm giờ cả sáu bước đến khi thư mục dist xuất hiện; ghi lại bước nào tốn thời gian nhất.
- Cố tình gõ status: "loadng" sai chính tả trong App.tsx, xem dev server có báo không; rồi chạy npx tsc --noEmit và chỉnh lệnh cho đến khi nó thực sự bắt được lỗi đó.
- Build xong, tìm trong thư mục dist mọi chuỗi trùng với giá trị biến VITE_ của bạn để tự kiểm bundle chứa những gì.

## Nguồn

- [Getting Started | Vite](https://vite.dev/guide/)

- [Start a New React Project – React](https://react.dev/learn/start-a-new-react-project)

- [Using TypeScript – React](https://react.dev/learn/typescript)

- [Features | Vite](https://vite.dev/guide/features)

- [Deploying a Static Site | Vite](https://vite.dev/guide/static-deploy)

- [Env Variables and Modes | Vite](https://vite.dev/guide/env-and-mode)
