Bước 2 - Làm giao diện cho Mini App
Một project Zalo Mini App được khởi tạo sẽ đi kèm với ZaUI. ZaUI cung cấp các thành phần giao diện phổ biến như Button, Input, Modal, Bottom Sheet,... giúp bạn có thể nhanh chóng xây dựng một ứng dụng hoàn chỉnh trong một thời gian ngắn.
Sử dụng hiệu quả ZaUI components sẽ giúp bạn tiết kiệm được nhiều thời gian để làm các logic hiển thị và có nhiều thời gian hơn để thực hiện các logic chức năng.
1. Cấu hình Mini App
Bản chất một Zalo Mini App được xây dựng với ZaUI là một React App, src/app.ts
có thể xem là entry point của ứng dụng:
Tutorial này giả sử bạn sử dụng TypeScript, phần mở rộng các file được đề cập tới là ts
hoặc tsx
. Nếu bạn sử dụng JavaScript, các file tương ứng sẽ có đuôi js
hoặc jsx
.
import App from "./components/app";
const root = createRoot(document.getElementById("app")!);
root.render(React.createElement(App));
Bạn có thể thấy component App
được sử dụng làm root của ứng dụng. Trong file src/components/app.tsx
, chúng ta sử dụng <Route>
để đăng ký các đường dẫn cho các trang mà người dùng có thể điều hướng đến trong Mini App của bạn. Ví dụ trong Coffee Shop, chúng ta sẽ có những page này:
const App = () => {
return (
<RecoilRoot>
<App>
<SnackbarProvider>
<ZMPRouter>
<AnimationRoutes>
<Route path="/" element={<HomePage />}></Route>
<Route path="/category" element={<CategoryPage />}></Route>
<Route path="/search" element={<SearchPage />}></Route>
<Route
path="/notification"
element={<NotificationPage />}
></Route>
<Route path="/cart" element={<CartPage />}></Route>
<Route path="/profile" element={<ProfilePage />}></Route>
</AnimationRoutes>
</ZMPRouter>
</SnackbarProvider>
</App>
</RecoilRoot>
);
};
export default App;
2. Tạo pages
Các đường dẫn được định nghĩa trong ứng dụng tương ứng với một page
trong mã nguồn của chúng ta. Tất cả các page
trong ứng dụng thường được tổ chức trong thư mục src/pages/
. Ví dụ, chúng ta có file src/pages/index.tsx
cho đường dẫn /
, src/pages/category.tsx
cho /category
,...
Mỗi file trong thư mục src/pages/
là một component React đơn giản, được sử dụng để hiển thị nội dung cho từng đường dẫn:
import React, { FC } from "react";
import { Header, Page } from "zmp-ui";
const CategoryPage: FC = () => {
return (
<Page>
<Header title="Danh mục" />
</Page>
);
};
export default CategoryPage;
Nếu trang của bạn có cấu trúc phức tạp, bạn có thể tạo một thư mục riêng để tổ chức các thành phần giao diện trong trang đó. Thư mục này bao gồm một file index.tsx
để chứa component page chính và các thành phần giao diện khác trong trang, mỗi thành phần nằm trong một file riêng biệt
src/
└── pages/
└── cart/
├── cart-items.tsx
├── delivery.tsx
├── index.tsx
├── person-picker.tsx
├── preview.tsx
├── store-picker.tsx
├── term-and-policies.tsx
└── time-picker.tsx
Bên trong src/pages/cart/index.tsx
:
import React, { FC } from "react";
import { Header, Page } from "zmp-ui";
import { useVirtualKeyboardVisible } from "hooks";
import { Divider } from "components/divider";
import { CartItems } from "./cart-items";
import { CartPreview } from "./preview";
import { TermsAndPolicies } from "./term-and-policies";
import { Delivery } from "./delivery";
const CartPage: FC = () => {
const keyboardVisible = useVirtualKeyboardVisible();
return (
<Page>
<Header title="Giỏ hàng" />
<CartItems />
<Delivery />
<Divider />
<TermsAndPolicies />
{!keyboardVisible && <CartPreview />}
</Page>
);
};
export default CartPage;
3. Tạo components
Cả pages
và components
đều là các React component. Tuy nhiên, pages thường được đăng ký trực tiếp vào router của Mini App và hiển thị một màn hình đầy đủ, trong khi các components thường hiển thị một phần nhỏ trên giao diện và có thể được sử dụng lại ở nhiều nơi khác nhau trong ứng dụng. Ví dụ:
Trang chủ / Danh mục / Kết quả tìm kiếm | Giỏ hàng |
---|---|
![]() | ![]() |
Ta có thể thiết kế component ProductPicker
để hiển thị bottom sheet thêm vào giỏ hàng như trên. Component này được thiết kế để có thể mở/đóng khi người dùng thao tác gì đó đối với các element bên trong, chẳng hạn:
// mở hộp thoại Thêm vào giỏ hàng khi nhấn vào một sản phẩm trong lưới
<ProductPicker product={product}>
{({ open }) => <div onClick={open}>
<b>Sản phẩm: {product.name}</b>
<u>Giá: {product.price}</u>
</div>}
</ProductPicker>
// hay chỉnh sửa một dòng bên trong giỏ hàng...
<ProductPicker product={product}>
{({ open, close }) => <Cart
onEdit={item => {
setProduct(item);
open();
}}
onEsc={close}
/>}
</ProductPicker>
Bên trong src/components/product-picker.tsx
:
export interface ProductPickerProps {
product?: Product;
children: (renderProps: { open: () => void; close: () => void }) => ReactNode;
}
export const ProductPicker: FC<ProductPickerProps> = ({
children,
product,
}) => {
const [visible, setVisible] = useState(false);
const addToCart = () => {
// business logic
setVisible(false);
};
return (
<>
{children({
open: () => setVisible(true),
close: () => setVisible(false),
})}
<Sheet visible={visible} onClose={() => setVisible(false)} autoHeight>
<Button
variant="primary"
type="highlight"
fullWidth
onClick={addToCart}
>
Thêm vào giỏ hàng
</Button>
</Sheet>
</>
);
};
Không phải lúc nào components cũng phải mang những logic phức tạp, component đôi khi có thể đơn giản như thế này:
export const DisplayPrice: FC<{ children: number }> = ({ children }) => {
return <>{children.toLocaleString()}đ</>;
};
miễn là nó có thể giúp bạn tái sử dụng code / hạn chế trùng lặp code trong project của mình:
<DisplayPrice>25000</DisplayPrice>
<DisplayPrice>{product.price}</DisplayPrice>
<DisplayPrice>{cart.total}</DisplayPrice>
4. Best practices
Phần lớn thời gian phát triển Zalo Mini App thường là thời gian bạn bỏ ra để làm giao diện. Trong bài hướng dẫn này, chúng tôi sẽ tập trung vào những khái niệm chính trong quá trình phát triển Zalo Mini App, mà không đi sâu vào từng chi tiết của template. Bạn có thể tham khảo thêm những bài viết sau đây để tối ưu hoá giao diện cho Mini App của mình: