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

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:
- 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ế.
- 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ụngnpm init
để chạy góicreate-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 trọng!
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx" // Quy định cách xử lý JSX
},
"include": ["src"], // Bao gồm các file trong thư mục src
"references": [{ "path": "./tsconfig.node.json" }] // Có thể có thêm cấu hì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 props và state.
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>Có vẻ bạn đã {age} tuổi.</p>}
{isStudent ? (
<p>Bạn là sinh viên.</p>
) : (
<p>Bạn không phải là 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à componentGreeting
mong đợi. name: string;
chỉ ra rằng propname
là bắt buộc và phải có kiểu dữ liệu làstring
.age?: number;
chỉ ra rằng propage
là tù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 propisStudent
là bắt buộc và phải có kiểu dữ liệu làboolean
.const Greeting: React.FC<GreetingProps> = ...
sử dụng kiểu genericReact.FC
(hoặc chỉReact.FunctionComponent
) và truyền interfaceGreetingProps
vào bên trong dấu ngoặc nhọn<>
. Điều này báo cho TypeScript biết rằngGreeting
là một functional component nhận props theo cấu trúc củaGreetingProps
.
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ằngcount
là mộtnumber
vì giá trị khởi tạo là0
. Bạn không cần ghiuseState<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 statemessage
làstring | null
. Đây là một union type, có nghĩa là statemessage
có thể là một chuỗi (string
) hoặc lànull
. Ban đầu, nó lànull
. Khi gọisetMessage
, TypeScript sẽ kiểm tra xem giá trị bạn truyền vào có phải làstring
hoặcnull
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 stateitems
là một mảng ([]
) các object có cấu trúc theo interfaceItem
(Item[]
). Khi gọisetItems
, 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