Bài 29.1: Typing getStaticProps và getStaticPaths
Bài 29.1: Typing getStaticProps và getStaticPaths
Chào mừng bạn trở lại với hành trình khám phá Next.js! Sau khi đã làm quen với sức mạnh của Static Site Generation (SSG) thông qua getStaticProps và getStaticPaths, bước tiếp theo để làm chủ môi trường phát triển hiện đại chính là tích hợp TypeScript. Việc gõ kiểu (typing) cho các hàm dữ liệu như getStaticProps và getStaticPaths không chỉ giúp code của bạn an toàn hơn, dễ bảo trì hơn mà còn mang lại trải nghiệm tuyệt vời nhờ tính năng tự động hoàn thành (autocompletion) và phát hiện lỗi sớm từ trình soạn thảo.
Trong bài viết này, chúng ta sẽ đi sâu vào cách tận dụng TypeScript để định nghĩa kiểu cho getStaticProps và getStaticPaths, đảm bảo rằng dữ liệu bạn nhận được và xử lý luôn đúng định dạng mong muốn. Hãy cùng bắt đầu!
Tại sao phải Typing getStaticProps và getStaticPaths?
Next.js cung cấp các kiểu dựng sẵn (built-in types) cho các hàm này, giúp chúng ta dễ dàng tích hợp TypeScript. Lợi ích của việc này bao gồm:
- An toàn kiểu (Type Safety): Ngăn chặn các lỗi runtime phổ biến liên quan đến việc truy cập các thuộc tính không tồn tại hoặc sai kiểu dữ liệu.
- Tự động hoàn thành (Autocompletion): Trình soạn thảo của bạn (như VS Code) có thể hiểu rõ cấu trúc dữ liệu bạn đang làm việc, cung cấp gợi ý code thông minh, giúp viết code nhanh hơn và chính xác hơn.
- Phát hiện lỗi sớm: TypeScript sẽ báo lỗi ngay trong quá trình phát triển nếu bạn sử dụng dữ liệu sai cách, thay vì đợi đến lúc chạy ứng dụng.
- Dễ đọc và bảo trì: Việc định rõ kiểu dữ liệu giúp các nhà phát triển khác (hoặc chính bạn trong tương lai) dễ dàng hiểu code mong đợi kiểu dữ liệu nào.
Typing getStaticProps
Hàm getStaticProps được sử dụng để tìm nạp dữ liệu tại thời điểm build (build time) và truyền nó dưới dạng props tới component Page. Khi sử dụng TypeScript, chúng ta cần định nghĩa kiểu cho cả dữ liệu trả về (các props) và tham số đầu vào (context).
Next.js cung cấp kiểu GetStaticProps từ package next.
1. Định nghĩa kiểu cho Props
Đầu tiên, bạn cần định nghĩa kiểu cho dữ liệu mà getStaticProps sẽ trả về và component Page sẽ nhận được. Thường thì chúng ta sử dụng một interface.
interface Product {
id: number;
name: string;
price: number;
}
interface ProductPageProps {
product: Product;
}
Trong ví dụ này, Product là kiểu dữ liệu cho một sản phẩm, và ProductPageProps là kiểu cho toàn bộ props mà component Page sẽ nhận (ở đây là một object chứa thuộc tính product có kiểu Product).
2. Sử dụng kiểu GetStaticProps
Bây giờ, chúng ta sẽ sử dụng kiểu GetStaticProps và truyền kiểu ProductPageProps mà chúng ta vừa tạo vào đó.
// pages/products/[id].tsx
import { GetStaticProps, GetStaticPropsContext } from 'next';
import { ParsedUrlQuery } from 'querystring';
interface Product {
id: number;
name: string;
price: number;
}
interface ProductPageProps {
product: Product;
}
// Component Page nhận props đã được typed
const ProductPage: React.FC<ProductPageProps> = ({ product }) => {
return (
<div>
<h1>{product.name}</h1>
<p>Price: ${product.price}</p>
</div>
);
};
// Typing cho getStaticProps
// <ProductPageProps> là kiểu của object được trả về trong 'props'
// <ParsedUrlQuery> là kiểu mặc định cho context.params
export const getStaticProps: GetStaticProps<ProductPageProps, ParsedUrlQuery> = async (context) => {
// Giả lập tìm nạp dữ liệu từ API hoặc file
const productId = context.params?.id; // context.params có kiểu ParsedUrlQuery
// Trong thực tế, bạn sẽ gọi API hoặc đọc file dựa vào productId
const dummyProduct: Product = {
id: Number(productId),
name: `Sample Product ${productId}`,
price: 19.99 * Number(productId),
};
// Kiểm tra nếu không tìm thấy sản phẩm (ví dụ thực tế phức tạp hơn)
if (!dummyProduct || isNaN(dummyProduct.id)) {
return {
notFound: true, // Trả về trang 404
}
}
return {
props: {
product: dummyProduct,
},
// revalidate: 60 // Optional: Incremental Static Regeneration
};
};
// getStaticPaths cũng cần được export cho dynamic routes
export { getStaticPaths } from './'; // Sẽ giải thích ở phần sau
export default ProductPage;
Giải thích:
- Chúng ta import
GetStaticPropsvàGetStaticPropsContexttừnext. GetStaticPropslà một Generic Type. Chúng ta truyềnProductPagePropsvào làm tham số Generic đầu tiên (<ProductPageProps>). Điều này cho TypeScript biết rằng hàmgetStaticPropsnày sẽ trả về một object mà sau đó sẽ được truyền vào component Page dưới dạngpropscó kiểuProductPageProps.- Tham số Generic thứ hai của
GetStaticPropslà kiểu chocontext.params. Mặc định, nó làParsedUrlQuerytừ modulequerystringcủa Node.js.ParsedUrlQueryvề cơ bản là một object nơi các giá trị có thể làstringhoặcstring[]hoặcundefined. - Bên trong hàm
getStaticProps, khi truy cậpcontext.params, TypeScript đã biết rằng nó có kiểuParsedUrlQuery. Nếu bạn biết chắc chắn kiểu của các tham số trong dynamic route, bạn có thể định nghĩa một
interfacecụ thể hơn chocontext.paramsđể có tính năng autocompletion tốt hơn:interface ProductParams extends ParsedUrlQuery { id: string; // Định nghĩa rõ 'id' là string } export const getStaticProps: GetStaticProps<ProductPageProps, ProductParams> = async (context) => { const productId = context.params?.id; // Bây giờ TypeScript biết productId là string | undefined // ... phần còn lại tương tự };Sử dụng
extends ParsedUrlQuerylà một cách hay để đảm bảo tương thích nếu sau này Next.js thêm thuộc tính mới vàocontext.params.- Kiểu trả về của
getStaticPropscũng được kiểm tra. TypeScript đảm bảo rằng object bạn trả về từreturn { props: ... }phải có thuộc tínhpropschứa dữ liệu có kiểuProductPageProps. Nó cũng hỗ trợ các trường hợp trả vềnotFound: truehoặcredirect: { destination: ..., permanent: ... }.
Typing getStaticPaths
Hàm getStaticPaths được sử dụng trong các dynamic routes (ví dụ: pages/posts/[slug].tsx) để khai báo những đường dẫn (paths) nào cần được render tĩnh tại thời điểm build. Tương tự như getStaticProps, chúng ta cũng có kiểu GetStaticPaths từ package next.
1. Sử dụng kiểu GetStaticPaths
Kiểu GetStaticPaths cũng là một Generic Type. Tham số Generic của nó là kiểu của các params trong mỗi object path mà bạn trả về.
// pages/posts/[slug].tsx
import { GetStaticPaths, GetStaticPathsContext } from 'next';
import { ParsedUrlQuery } from 'querystring';
// Định nghĩa kiểu cho params trong path
interface PostParams extends ParsedUrlQuery {
slug: string;
}
// Typing cho getStaticPaths
// <PostParams> là kiểu của object context.params VÀ kiểu của object params trong mỗi item của mảng 'paths'
export const getStaticPaths: GetStaticPaths<PostParams> = async () => {
// Giả lập tìm nạp danh sách slugs từ API hoặc file
const posts = [
{ slug: 'bai-viet-1' },
{ slug: 'bai-viet-2' },
{ slug: 'bai-viet-3' },
];
// Chuyển đổi danh sách posts thành định dạng paths mà Next.js yêu cầu
const paths = posts.map(post => ({
params: {
slug: post.slug, // TypeScript kiểm tra slug có kiểu PostParams['slug'] (string) không
},
}));
return {
paths,
fallback: false, // Hoặc 'blocking' hoặc true
};
};
// getStaticProps cho trang post cũng cần được export và typed
// ... (phần getStaticProps tương tự như ví dụ Product ở trên, sử dụng PostParams)
// export const getStaticProps: GetStaticProps<PostPageProps, PostParams> = async (context) => { ... }
// Component Page
// ... (phần component React nhận props)
// export default PostPage;
Giải thích:
- Chúng ta import
GetStaticPathsvàGetStaticPathsContexttừnext. - Kiểu
GetStaticPathsnhận một tham số Generic (<PostParams>) là kiểu của objectparamsmà bạn sẽ trả về trong mỗi phần tử của mảngpaths. Điều này giúp TypeScript xác minh rằng cấu trúc củapathslà chính xác. - Chúng ta định nghĩa
interface PostParamsđể mô tả cấu trúc của objectparams, ở đây chỉ có thuộc tínhslugkiểustring. - Bên trong
getStaticPaths, khi chúng ta tạo mảngpaths, TypeScript kiểm tra từng objectparamsbên trong và đảm bảo nó khớp với kiểuPostParamsđã khai báo. - Nếu bạn không truyền tham số Generic nào cho
GetStaticPaths(ví dụ:const getStaticPaths: GetStaticPaths = async () => { ... }), TypeScript sẽ sử dụng kiểu mặc định (ParsedUrlQuery) choparams, nhưng việc định nghĩa kiểu tường minh giúp code rõ ràng hơn và cung cấp autocompletion tốt hơn khi làm việc vớiparams. - Kiểu trả về của
getStaticPaths({ paths: ..., fallback: ... }) cũng được TypeScript kiểm tra để đảm bảo đúng cấu trúc.
Tóm lược lợi ích
Việc dành thời gian để gõ kiểu cho getStaticProps và getStaticPaths ngay từ đầu sẽ mang lại nhiều lợi ích đáng kể cho dự án Next.js sử dụng TypeScript của bạn:
- Giảm thiểu lỗi: Bắt được các lỗi liên quan đến dữ liệu sai kiểu hoặc thiếu thuộc tính ngay trong quá trình code.
- Nâng cao năng suất: Autocompletion giúp bạn viết code nhanh hơn và ít phải tra cứu tài liệu.
- Cải thiện khả năng bảo trì: Code trở nên rõ ràng, dễ hiểu và dễ thay đổi hơn trong tương lai.
- Hỗ trợ Refactoring: Khi bạn thay đổi cấu trúc dữ liệu, TypeScript sẽ báo cáo tất cả những nơi cần cập nhật.
Bằng cách áp dụng các kiểu GetStaticProps và GetStaticPaths cùng với việc định nghĩa các interface cho dữ liệu của bạn, bạn đang xây dựng một ứng dụng Next.js mạnh mẽ, đáng tin cậy và dễ làm việc hơn rất nhiều. Đây là một bước tiến quan trọng trong việc phát triển các ứng dụng web hiệu quả với Next.js và TypeScript.
Comments