Bài 15.1: Cài đặt React với TypeScript

Chào mừng trở lại với chuỗi bài viết về Lập trình Web Front-end! Sau khi đã làm quen với những khái niệm cơ bản của React, hôm nay chúng ta sẽ cùng nhau nâng cấp sức mạnh và độ tin cậy cho các dự án React của mình bằng cách kết hợp với TypeScript.

TypeScript là một siêu tập hợp (superset) của JavaScript, bổ sung tính năng kiểu tĩnh (static typing) vào ngôn ngữ. Điều này có nghĩa là chúng ta có thể định nghĩa rõ ràng kiểu dữ liệu cho biến, props, state, và các giá trị khác trong ứng dụng của mình ngay từ khi viết code. Lợi ích mang lại là vô cùng lớn:

  • Bắt lỗi sớm: TypeScript giúp phát hiện lỗi liên quan đến kiểu dữ liệu trước khi bạn chạy ứng dụng, tiết kiệm thời gian debug.
  • Nâng cao khả năng đọc hiểu: Việc định nghĩa kiểu rõ ràng giúp các thành viên trong team (hoặc chính bạn sau này) dễ dàng hiểu mục đích và cách sử dụng của từng phần code.
  • Hỗ trợ công cụ mạnh mẽ: Các trình soạn thảo code như VS Code có thể cung cấp tính năng tự động hoàn thành (autocompletion) chính xác hơn, kiểm tra lỗi trực tiếp (linting), và điều hướng code tốt hơn nhờ thông tin kiểu dữ liệu.
  • Tái cấu trúc dễ dàng hơn: Khi bạn cần thay đổi cấu trúc dữ liệu, TypeScript sẽ báo cho bạn biết tất cả những nơi bị ảnh hưởng, giúp quá trình refactor trở nên an toàn hơn.

Tóm lại, sử dụng TypeScript với React là một bước đi thông minh để xây dựng các ứng dụng front-end lớn hơn, phức tạp hơn và dễ bảo trì hơn.

Vậy làm thế nào để bắt đầu?

Chuẩn bị trước khi bắt đầu

Trước khi chúng ta bắt đầu cài đặt, hãy đảm bảo rằng bạn đã cài đặt các công cụ cần thiết trên máy tính của mình:

  1. Node.js và npm/yarn/pnpm: TypeScript và React đều chạy trên môi trường Node.js. Hãy chắc chắn bạn đã cài đặt Node.js (phiên bản LTS được khuyến nghị). Việc cài đặt Node.js cũng sẽ đi kèm với npm (Node Package Manager). Bạn cũng có thể chọn sử dụng yarn hoặc pnpm thay thế.
  2. Trình soạn thảo code (IDE): Một IDE hỗ trợ tốt TypeScript và React sẽ giúp bạn làm việc hiệu quả hơn rất nhiều. Visual Studio Code (VS Code) là lựa chọn phổ biến nhất và được hỗ trợ rất tốt.

Cài đặt Dự án React với TypeScript

Có một vài cách để khởi tạo một dự án React tích hợp TypeScript. Hai phương pháp phổ biến hiện nay là sử dụng Create React App (phương pháp truyền thống nhưng ít dùng dần) và Vite (phương pháp hiện đại, nhanh hơn). Chúng ta sẽ tập trung vào Vite vì nó mang lại trải nghiệm phát triển vượt trội về tốc độ.

Cách 1: Sử dụng Vite (Khuyến nghị)

Vite là một công cụ build cực kỳ nhanh, được thiết kế để cung cấp trải nghiệm phát triển front-end nhanh chóng. Nó hỗ trợ sẵn TypeScript mà không cần cấu hình phức tạp.

Mở Terminal hoặc Command Prompt và chạy lệnh sau:

npm create vite@latest my-react-ts-app --template react-ts
  • npm create vite@latest: Lệnh này sử dụng npm init để chạy gói create-vite phiên bản mới nhất.
  • my-react-ts-app: Đây là tên thư mục cho dự án của bạn. Bạn có thể thay đổi nó tùy ý.
  • --template react-ts: Flag này chỉ định Vite sử dụng template (mẫu) là React với TypeScript.

Vite có thể sẽ hỏi bạn xác nhận một vài lựa chọn, cứ nhấn Enter để tiếp tục với template react-ts.

Sau khi lệnh chạy xong, điều hướng vào thư mục dự án và cài đặt các dependency:

cd my-react-ts-app
npm install  # Hoặc yarn install, pnpm install tùy trình quản lý gói bạn dùng

Vậy là xong! Dự án React với TypeScript của bạn đã sẵn sàng.

Cách 2: Sử dụng Create React App (CRA)

Create React App là công cụ chính thức của React để tạo dự án đơn giản. Nó cũng hỗ trợ template TypeScript:

npx create-react-app my-react-ts-app --template typescript
  • npx create-react-app: Sử dụng npx để chạy gói create-react-app mà không cần cài đặt global.
  • my-react-ts-app: Tên thư mục dự án.
  • --template typescript: Chỉ định template TypeScript.

CRA sẽ mất một chút thời gian để cài đặt tất cả các dependency. Sau khi hoàn tất, điều hướng vào thư mục dự án:

cd my-react-ts-app

Bạn đã sẵn sàng!

Lưu ý: Như đã đề cập, Vite đang trở nên phổ biến hơn do tốc độ build và hot module replacement (HMR) vượt trội so với CRA truyền thống (đặc biệt trong các dự án lớn). Tuy nhiên, CRA vẫn là một lựa chọn tốt cho người mới bắt đầu hoặc các dự án nhỏ.

Khám phá cấu trúc dự án

Sau khi cài đặt bằng một trong hai phương pháp trên, bạn sẽ thấy cấu trúc thư mục khá quen thuộc nếu đã làm việc với React, nhưng có thêm một vài điểm khác biệt quan trọng:

  • src/: Thư mục chứa mã nguồn chính của bạn.
  • .tsx file: Thay vì .jsx cho các component React chứa JSX, bạn sẽ sử dụng .tsx. Đây là quy ước để TypeScript biết rằng file này chứa cả mã TypeScript và JSX.
  • tsconfig.json: Đây là file cấu hình cho trình biên dịch TypeScript. Nó quy định các quy tắc và tùy chọn khi TypeScript kiểm tra và biên dịch code của bạn.

Hãy cùng xem qua file tsconfig.json một chút. Bạn không cần hiểu hết mọi dòng trong file này ngay lập tức, nhưng biết sự tồn tại và mục đích của nó là quan trọng. File này định nghĩa cách TypeScript hoạt động trong dự án của bạn, ví dụ như:

  • "target": Phiên bản JavaScript mà code TypeScript sẽ được biên dịch sang (ví dụ: "es2020").
  • "module": Hệ thống module được sử dụng (ví dụ: "ESNext").
  • "jsx": Cách xử lý cú pháp JSX (ví dụ: "react-jsx").
  • "strict": Đây là một tùy chọn cực kỳ quan trọng. Khi "strict": true, TypeScript sẽ áp dụng một bộ quy tắc kiểm tra nghiêm ngặt hơn, giúp phát hiện nhiều loại lỗi tiềm ẩn. Rất khuyến khích để nó là true trong các dự án mới.
// Một phần của tsconfig.json (có thể hơi khác tùy template)
{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true, // Rất quan trng!
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx" // Quy định cách xử  JSX
  },
  "include": ["src"], // Bao gồm các file trong t mục src
  "references": [{ "path": "./tsconfig.node.json" }] //  thể  thêm cấu nh cho môi trường Node
}

Viết Component đầu tiên với TypeScript

Bây giờ, hãy xem cách chúng ta viết một component React đơn giản và sử dụng TypeScript để định nghĩa kiểu dữ liệu cho propsstate.

Mở file src/App.tsx (hoặc tạo một component mới trong src/components).

Định nghĩa Kiểu cho Props

Với JavaScript React, props thường là các object đơn thuần. Với TypeScript, chúng ta sẽ định nghĩa cấu trúc của object props đó bằng interface hoặc type.

Ví dụ, tạo một component Greeting nhận prop name là string:

// src/components/Greeting.tsx
import React from 'react';

// Định nghĩa cấu trúc props bằng interface
interface GreetingProps {
  name: string;         // Prop 'name' bắt buộc, kiểu string
  age?: number;         // Prop 'age' tùy chọn (có dấu ?), kiểu number
  isStudent: boolean;   // Prop 'isStudent' bắt buộc, kiểu boolean
}

// Sử dụng React.FC (Functional Component) và truyền kiểu props vào <>
const Greeting: React.FC<GreetingProps> = ({ name, age, isStudent }) => {
  return (
    <div>
      <h2>Xin chào, {name}!</h2>
      {/* Chỉ hiển thị tuổi nếu prop age tồn tại */}
      {age && <p> vẻ bạn đã {age} tuổi.</p>}
      {isStudent ? (
        <p>Bạn  sinh viên.</p>
      ) : (
        <p>Bạn không phải  sinh viên.</p>
      )}
    </div>
  );
};

export default Greeting;

Giải thích code:

  • Chúng ta định nghĩa một interface GreetingProps để mô tả các props mà component Greeting mong đợi.
  • name: string; chỉ ra rằng prop namebắt buộc và phải có kiểu dữ liệu là string.
  • age?: number; chỉ ra rằng prop agetùy chọn (?) và nếu được truyền, nó phải có kiểu dữ liệu là number.
  • isStudent: boolean; chỉ ra rằng prop isStudentbắt buộc và phải có kiểu dữ liệu là boolean.
  • const Greeting: React.FC<GreetingProps> = ... sử dụng kiểu generic React.FC (hoặc chỉ React.FunctionComponent) và truyền interface GreetingProps vào bên trong dấu ngoặc nhọn <>. Điều này báo cho TypeScript biết rằng Greeting là một functional component nhận props theo cấu trúc của GreetingProps.

Khi bạn sử dụng component này ở nơi khác (ví dụ App.tsx), TypeScript sẽ kiểm tra xem bạn có truyền đúng và đủ các props bắt buộc với kiểu dữ liệu chính xác hay không. Nếu thiếu name hoặc isStudent, hoặc truyền sai kiểu dữ liệu, bạn sẽ nhận được lỗi ngay trong trình soạn thảo trước khi chạy code!

// src/App.tsx
import React from 'react';
import Greeting from './components/Greeting';

function App() {
  return (
    <div className="App">
      <h1>ng dụng của tôi</h1>
      {/* Sử dụng component Greeting với props đúng kiểu */}
      <Greeting name="Độc giả thân mến" age={30} isStudent={false} />

      {/* TypeScript sẽ báo lỗi ở đây vì thiếu prop 'isStudent' bắt buộc */}
      {/* <Greeting name="Người dùng khác" age={25} /> */}

      {/* TypeScript cũng sẽ báo lỗi ở đây vì prop 'name' sai kiểu */}
      {/* <Greeting name={123} isStudent={true} /> */}
    </div>
  );
}

export default App;
Định nghĩa Kiểu cho State

Tương tự như props, bạn cũng nên định nghĩa kiểu dữ liệu cho state khi sử dụng hook useState. TypeScript có thể tự động suy luận kiểu cho state nếu bạn khởi tạo nó với một giá trị ban đầu (ví dụ: useState(0) thì state có kiểu number). Tuy nhiên, việc tường minh chỉ định kiểu là tốt hơn, đặc biệt khi state có thể là nhiều kiểu (union type) hoặc có giá trị ban đầu là null hoặc undefined.

Ví dụ về state với TypeScript:

// src/App.tsx (tiếp tục hoặc trong một component khác)
import React, { useState } from 'react';
// import Greeting from './components/Greeting'; // Nếu chưa import

function App() {
  // state 'count': khởi tạo là 0, TypeScript suy luận kiểu là number
  const [count, setCount] = useState(0); // state có kiểu number

  // state 'message': tường minh chỉ định kiểu là string hoặc null
  const [message, setMessage] = useState<string | null>(null);

  // state 'items': tường minh chỉ định kiểu là một mảng các object
  interface Item {
    id: number;
    name: string;
  }
  const [items, setItems] = useState<Item[]>([]); // state là mảng các Item

  const handleIncrement = () => {
    setCount(count + 1); // Hoạt động bình thường vì count là number
  };

  const handleSetMessage = () => {
    setMessage("Đã cập nhật thông báo!"); //setMessage chỉ nhận string hoặc null
    // setMessage(123); // TypeScript sẽ báo lỗi ở đây
  };

  const handleAddItem = () => {
    const newItem: Item = { id: items.length + 1, name: `Item ${items.length + 1}` };
    setItems([...items, newItem]); // setItems chỉ nhận một mảng Item
    // setItems("abc"); // TypeScript sẽ báo lỗi ở đây
  };

  return (
    <div className="App">
      <h1>ng dụng với State & TypeScript</h1>

      {/* ... (phần sử dụng component Greeting nếu có) ... */}

      <p>Số đếm: **{count}**</p>
      <button onClick={handleIncrement}>Tăng</button>

      {message && <p>Thông báo: *{message}*</p>}
      <button onClick={handleSetMessage}>Đặt thông báo</button>

      <h2>Danh sách Item</h2>
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.id}: **{item.name}**
          </li>
        ))}
      </ul>
      <button onClick={handleAddItem}>Thêm Item</button>

    </div>
  );
}

export default App;

Giải thích code:

  • const [count, setCount] = useState(0);: TypeScript tự động suy luận rằng count là một number vì giá trị khởi tạo là 0. Bạn không cần ghi useState<number>(0) trong trường hợp đơn giản này, nhưng việc ghi tường minh cũng không sai và giúp code rõ ràng hơn.
  • const [message, setMessage] = useState<string | null>(null);: Ở đây, chúng ta tường minh chỉ định kiểu dữ liệu cho state messagestring | null. Đây là một union type, có nghĩa là state message có thể là một chuỗi (string) hoặc là null. Ban đầu, nó là null. Khi gọi setMessage, TypeScript sẽ kiểm tra xem giá trị bạn truyền vào có phải là string hoặc null hay không.
  • interface Item { id: number; name: string; }: Chúng ta định nghĩa một interface để mô tả cấu trúc của mỗi item trong danh sách.
  • const [items, setItems] = useState<Item[]>([]);: Ở đây, chúng ta tường minh chỉ định state items là một mảng ([]) các object có cấu trúc theo interface Item (Item[]). Khi gọi setItems, TypeScript sẽ đảm bảo bạn truyền vào một mảng tuân thủ cấu trúc đó.

Việc sử dụng TypeScript giúp bạn chắc chắn rằng state của bạn luôn có kiểu dữ liệu mà bạn mong đợi, tránh được các lỗi runtime khó chịu do dữ liệu không đúng định dạng.

Chạy ứng dụng

Để xem kết quả, bạn chỉ cần chạy lệnh start/dev của dự án:

  • Nếu dùng Vite:
    npm run dev
    
  • Nếu dùng Create React App:
    npm start
    

Ứng dụng sẽ được chạy trên môi trường phát triển, thường là tại http://localhost:5173/ (Vite) hoặc http://localhost:3000/ (CRA). Bạn có thể mở trình duyệt để xem và tương tác. Khi bạn gõ code TypeScript bị lỗi kiểu dữ liệu, trình soạn thảo và quá trình build/dev server sẽ báo lỗi cho bạn biết.

Comments

There are no comments at the moment.