FDE PulseViệc làm FDE đang mở 316Mới đăng 7 ngày qua 10Chủ đề nổi bật: Đào tạo kỹ năng FDE tại Đông Nam Á

Tờ báo của nghề Forward Deployed Engineer

Bách khoa

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.

Đồ hoạDev server bỏ qua gì, tsc bắt được gì
npm run dev (Vite)npx tsc --noEmit
Gõ sai status "loadng"Vẫn chạy, màn hình kẹt ở trạng thái lạBáo lỗi: giá trị không thuộc union Status
useState("idle") không khai kiểuVẫn chạy, mọi chuỗi đều được chấp nhậnKiểu bị suy thành string nên cũng không bắt được
Đọc sai trường, như answer.txtVẫn chạy, câu trả lời hiện trốngBáo lỗi: Answer không có trường đó
API key đặt trong biến VITE_Không báo, key bị đóng gói vào bundleKhông báo, chỉ bạn tự chặn được

Dev server chỉ dịch code nên lỗi kiểu phải chờ tsc mới lộ, còn key trong VITE_ thì không công cụ nào chặn hộ bạn.

Đồ hoạ: FDE Times

Tóm tắt nhanh

  • Vite cần Node.js 20.19+ hoặc 22.12+; kiểm tra node -v trước khi làm bất cứ điều gì khác.
  • Vite chỉ transpile TypeScript, không kiểm kiểu: chạy tsc --noEmit trước khi build ra dist, và tự gõ sai một chỗ để chắc lệnh đó bắt được lỗi.
  • Mọi biến VITE_ đều bị đóng gói vào mã client, nên API key phải nằm ở backend.
Chia sẻLinkedInFacebookX

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:

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):

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:

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:

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:

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ỉ.

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:

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 (
    <form onSubmit={handleSubmit}>
      <textarea name="question" rows={4} />
      <button disabled={ui.status === "loading"}>Hỏi</button>
      {ui.status === "error" && <p>Không gọi được API, thử lại.</p>}
      {ui.answer && <p>{ui.answer.text}</p>}
    </form>
  );
}

Để ý 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:

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.

6 nguồn
Đọc tiếp trên lộ trình · Chặng 2: Kỹ thuật rộngViết lớp gọi API cho agent TypeScript: timeout, retry và idempotency keyAgent của bạn gọi lại một API bị treo, và hệ thống khách hàng hoàn tiền hai lần; khoảng 80 dòng TypeScript là đủ để chuyện đó không xảy ra.