Component Vue tùy chỉnh trong UI sinh tự động — an toàn khi tái tạo
Học cách thêm component Vue tùy chỉnh vào UI sinh tự động mà không làm mất khả năng tái tạo: dùng mô hình cô lập, ranh giới rõ ràng và quy tắc bàn giao đơn giản.

Những gì bị hỏng khi bạn chỉnh sửa UI sinh tự động
UI sinh tự động được thiết kế để có thể xây lại. Trên các nền tảng như AppMaster, mã ứng dụng web Vue3 được sinh từ trình dựng trực quan. Tái tạo (regeneration) là cách giữ mọi thay đổi nhất quán giữa màn hình, logic và mô hình dữ liệu.
Vấn đề đơn giản là: nếu bạn chỉnh sửa các file được sinh ra, lần tái tạo tiếp theo có thể ghi đè những chỉnh sửa đó.
Đó là lý do nhóm hay thêm mã tùy chỉnh. Các khối UI sẵn có thường đủ cho form và bảng tiêu chuẩn, nhưng ứng dụng thực tế thường cần vài thành phần đặc biệt: biểu đồ phức tạp, bộ chọn trên bản đồ, trình soạn văn bản phong phú, bảng ký, hoặc công cụ lập kế hoạch kéo-thả. Những thứ này là lý do hợp lệ để thêm component Vue tùy chỉnh, miễn là bạn coi chúng như phần mở rộng, chứ không phải sửa trực tiếp.
Khi mã tùy chỉnh trộn lẫn với mã sinh, lỗi có thể đến muộn và khó hiểu. Bạn có thể không nhận ra cho đến khi thay đổi UI buộc phải tái tạo, hoặc khi đồng đội sửa một màn hình trong trình dựng trực quan. Những vấn đề thường gặp bao gồm:
- Markup tùy chỉnh biến mất vì template bị sinh lại.
- Import hoặc đăng ký bị vỡ do tên file hoặc cấu trúc thay đổi.
- Một "sửa nhỏ" trở thành xung đột merge ở mỗi lần triển khai.
- Logic sinh và logic tùy chỉnh trôi dạt, nên các trường hợp biên bắt đầu lỗi.
- Nâng cấp cảm thấy rủi ro vì bạn không biết gì sẽ bị thay thế.
Mục tiêu không phải tránh tùy chỉnh. Mục tiêu là làm cho việc xây lại trở nên dự đoán được. Nếu bạn giữ ranh giới rõ ràng giữa các màn hình sinh và widget tùy chỉnh, việc tái tạo sẽ trở nên bình thường thay vì căng thẳng.
Quy tắc ranh giới giữ tái tạo an toàn
Nếu muốn tùy chỉnh mà không mất việc, hãy theo một quy tắc: không bao giờ chỉnh sửa file sinh ra. Hãy coi chúng là đầu ra chỉ đọc, như một artifact đã biên dịch.
Hãy nghĩ UI của bạn gồm hai vùng:
- Vùng sinh ra: các trang, layout và màn hình do trình sinh tạo.
- Vùng tùy chỉnh: các component Vue bạn viết tay nằm trong thư mục riêng.
UI sinh ra nên tiêu thụ (consume) component tùy chỉnh của bạn. Đó không nên là nơi bạn xây dựng component đó.
Để điều này hoạt động lâu dài, giữ "biên" nhỏ và rõ. Một widget tùy chỉnh nên hành xử như một sản phẩm nhỏ với hợp đồng:
- Props vào: chỉ những gì nó cần để render.
- Events ra: chỉ những gì trang cần phản ứng.
Tránh truy cập trạng thái toàn cục hoặc gọi API không liên quan từ trong widget trừ khi điều đó rõ ràng là một phần của hợp đồng.
Với các màn hình Vue3 kiểu AppMaster, điều này thường có nghĩa bạn làm rất ít nối dây trong màn hình sinh ra để truyền props và xử lý events. Việc nối dây đó có thể thay đổi khi tái tạo, nhưng nó nhỏ và dễ làm lại. Công việc thực sự an toàn ở vùng tùy chỉnh.
Các mẫu cô lập hợp với Vue3
Mục tiêu rõ ràng: tái tạo nên thoải mái thay thế các file sinh ra, trong khi mã widget của bạn không bị động chạm.
Một cách thực tiễn là giữ các widget riêng biệt thành một module nội bộ nhỏ: components, style và tiện ích helper ở một chỗ. Trong ứng dụng Vue3 sinh ra, điều đó thường có nghĩa mã tùy chỉnh nằm ngoài các trang sinh ra và được import như một phụ thuộc.
Một component wrapper rất hữu ích. Hãy để wrapper nói chuyện với app sinh ra: đọc hình dạng dữ liệu hiện tại của trang, chuẩn hóa nó và truyền props sạch vào widget. Nếu sau này hình dạng dữ liệu thay đổi, bạn thường cập nhật wrapper thay vì viết lại widget.
Một vài mẫu chịu được thay đổi tốt:
- Đối xử với widget như hộp đen: props vào, events ra.
- Dùng wrapper để ánh xạ phản hồi API, ngày tháng và ID sang định dạng thân thiện với widget.
- Giữ style ở phạm vi (scoped) để các trang sinh không vô tình ghi đè widget.
- Đừng dựa vào cấu trúc DOM của phần tử cha hay các class trang cụ thể.
Về style, ưu tiên CSS scoped (hoặc CSS Modules) và namespace các class chia sẻ. Nếu widget cần khớp theme ứng dụng, truyền token theme như props (màu, khoảng cách, cỡ chữ) thay vì import style của trang.
Slots có thể an toàn khi chúng nhỏ và tùy chọn, ví dụ một thông báo "trạng thái rỗng". Nếu slots bắt đầu điều khiển layout cốt lõi hoặc hành vi, bạn đã đưa widget trở lại lớp sinh ra, nơi đau đầu khi tái tạo bắt đầu.
Thiết kế hợp đồng component ổn định (props và events)
Cách an toàn nhất để giữ tái tạo dễ chịu là coi mỗi widget như một giao diện ổn định. Màn hình sinh có thể thay đổi. Component của bạn thì không nên thay đổi liên tục.
Bắt đầu với inputs (props). Giữ chúng ít, dễ đoán và dễ kiểm tra. Ưu tiên các nguyên thủy đơn giản và object dạng plain bạn kiểm soát. Thêm giá trị mặc định để widget có hành vi tốt ngay cả khi trang chưa truyền gì. Nếu có thứ có thể sai định dạng (ID, chuỗi ngày, giá trị giống enum), hãy validate và thất bại nhẹ: hiển thị trạng thái rỗng thay vì crash.
Với outputs, chuẩn hóa events để widget cảm thấy nhất quán với phần còn lại của app. Một bộ sự kiện đáng tin cậy bao gồm:
update:modelValuecho v-modelchangecho thay đổi đã được người dùng xác nhận (không phải mỗi lần gõ)errorkhi component không thể hoàn thành nhiệm vụreadykhi công việc async kết thúc và widget sẵn dùng
Nếu có công việc bất đồng bộ, hãy làm nó thành một phần của hợp đồng. Phơi ra props như loading và disabled, và cân nhắc errorMessage cho lỗi phía máy chủ. Nếu component tự fetch dữ liệu, vẫn phát error và ready để parent có thể phản ứng (toast, logging, UI dự phòng).
Kỳ vọng về accessibility
Nhúng khả năng truy cập vào hợp đồng. Chấp nhận prop label (hoặc ariaLabel), ghi rõ hành vi bàn phím, và giữ focus dự đoán sau các hành động.
Ví dụ, một timeline trên dashboard nên hỗ trợ phím mũi tên để di chuyển giữa mục, Enter để mở chi tiết, và trả focus về điều khiển đã mở dialog khi dialog đóng. Điều này giúp widget tái sử dụng trên các màn hình sinh lại mà không cần làm lại nhiều.
Từng bước: thêm widget tùy chỉnh mà không chạm file sinh ra
Bắt đầu nhỏ: một màn hình người dùng quan tâm, một widget khiến màn hình khác biệt. Giữ thay đổi đầu tiên hẹp giúp dễ thấy tái tạo ảnh hưởng gì.
-
Tạo component ngoài khu vực sinh ra. Đặt nó trong thư mục bạn sở hữu và quản lý trong source control (thường là
customhoặcextensions). -
Giữ bề mặt công khai nhỏ. Vài props vào, vài events ra. Đừng truyền cả trạng thái trang.
-
Thêm một wrapper mỏng bạn cũng sở hữu. Nhiệm vụ của nó là dịch "dữ liệu trang sinh ra" thành hợp đồng widget.
-
Tích hợp qua điểm mở rộng được hỗ trợ. Tham chiếu wrapper theo cách không yêu cầu sửa file sinh ra.
-
Tái tạo và kiểm tra. Thư mục tùy chỉnh, wrapper và component của bạn nên nguyên vẹn và vẫn compile.
Giữ biên sắc nét. Widget tập trung vào hiển thị và tương tác. Wrapper ánh xạ dữ liệu và chuyển tiếp hành động. Quy tắc nghiệp vụ ở lớp logic của app (backend hoặc quy trình chia sẻ), không chôn trong widget.
Một kiểm tra hữu ích: nếu tái tạo xảy ra ngay bây giờ, liệu đồng đội có thể rebuild app và đạt cùng kết quả mà không phải làm lại chỉnh tay không? Nếu có, mẫu của bạn ổn.
Đặt logic ở đâu để UI dễ bảo trì
Widget tùy chỉnh nên chủ yếu lo việc hiển thị và phản ứng với nhập liệu người dùng. Càng nhồi nhiều quy tắc nghiệp vụ vào widget càng khó tái sử dụng, test và thay đổi.
Mặc định tốt là: giữ logic nghiệp vụ ở tầng trang hoặc feature, và giữ widget "ngu". Trang quyết định widget nhận dữ liệu gì và chuyện gì xảy ra khi widget phát event. Widget render và báo ý định người dùng.
Khi cần logic gần widget (định dạng, state nhỏ, validate client-side), che nó sau một lớp service nhỏ. Trong Vue3 đó có thể là một module, một composable, hoặc một store với API rõ ràng. Widget import API đó, không import lung tung các nội dung nội bộ của app.
Một phân tách thực tế:
- Widget (component): state UI, xử lý input, hiển thị, phát event như
select,change,retry. - Service/composable: định hình dữ liệu, cache, ánh xạ lỗi API thành thông điệp người dùng.
- Page/container: quy tắc nghiệp vụ, quyền, dữ liệu nào cần load, khi nào lưu.
- Các phần do trình sinh tạo: để nguyên; truyền dữ liệu vào và lắng nghe events.
Tránh gọi API trực tiếp từ widget trừ khi đó rõ ràng là hợp đồng của widget. Nếu nó tự fetch, hãy đặt tên rõ ràng (ví dụ CustomerSearchWidget) và giữ code gọi API ở một service duy nhất. Ngược lại, truyền items, loading, và error xuống như props.
Thông báo lỗi nên hướng đến người dùng và nhất quán. Thay vì hiển thị nguyên văn lỗi server, ánh xạ sang một tập nhỏ thông điệp dùng chung, ví dụ “Không thể tải dữ liệu. Thử lại.” Bao gồm hành động thử lại khi có thể, và log lỗi chi tiết ra ngoài widget.
Ví dụ: một widget ApprovalBadge tùy chỉnh không nên quyết định hóa đơn có thể phê duyệt hay không. Hãy để trang tính status và canApprove. Badge phát approve, và trang thực hiện quy tắc thực sự và gọi backend (ví dụ API bạn mô tả trong AppMaster), rồi truyền trạng thái thành công hay lỗi trở lại UI.
Các sai lầm thường gây phiền phức sau khi tái tạo
Hầu hết vấn đề không phải do Vue. Chúng đến từ việc trộn lẫn công việc tùy chỉnh vào nơi trình sinh sở hữu, hoặc dựa vào chi tiết dễ thay đổi.
Những sai lầm thường biến một widget nhỏ thành gánh nặng bảo trì:
- Sửa trực tiếp file Vue sinh ra rồi quên thay đổi gì.
- Dùng CSS toàn cục hoặc selector rộng làm ảnh hưởng các trang khác khi markup thay đổi.
- Đọc hoặc mutate trực tiếp hình dạng state sinh ra, nên một đổi tên nhỏ làm widget vỡ.
- Nhồi quá nhiều giả định trang-cụ thể vào một component.
- Thay đổi API component (props/events) mà không có kế hoạch migrate.
Một kịch bản phổ biến: bạn thêm widget bảng tùy chỉnh và nó chạy tốt. Một tháng sau, layout sinh ra thay đổi khiến quy tắc .btn toàn cục của bạn ảnh hưởng tới trang đăng nhập và admin. Hoặc object dữ liệu đổi từ user.name sang user.profile.name, widget im lặng thất bại. Vấn đề không phải widget, mà là phụ thuộc vào chi tiết không ổn định.
Hai thói quen ngăn phần lớn vấn đề này:
Thứ nhất, coi mã sinh ra là chỉ đọc và giữ file tùy chỉnh tách riêng, với ranh giới import rõ ràng.
Thứ hai, giữ hợp đồng component nhỏ và rõ ràng. Nếu cần phát triển nó, thêm prop version đơn giản (ví dụ apiVersion) hoặc hỗ trợ cả hai hình dạng prop cũ và mới trong một bản phát hành.
Danh kiểm nhanh trước khi phát hành component tùy chỉnh
Trước khi merge widget vào app Vue3 sinh ra, làm một kiểm tra thực tế nhanh. Nó nên sống sót qua lần tái tạo tiếp theo mà không cần anh hùng cứu trợ, và người khác có thể tái sử dụng.
- Test tái tạo: chạy tái tạo đầy đủ và build lại. Nếu bạn phải chỉnh sửa lại file sinh ra, ranh giới đang sai.
- Inputs và outputs rõ ràng: props vào, emits ra. Tránh phụ thuộc ma thuật như lấy DOM ngoài hoặc giả sử store trang cụ thể.
- Chứa style: scope style và dùng tiền tố class rõ ràng (ví dụ
timeline-). - Mọi trạng thái trông ổn: loading, error và empty state nên có và hợp lý.
- Tái sử dụng không cần clone: xác nhận có thể thả widget vào trang khác bằng cách thay props và handler, không phải sao chép nội dung.
Cách kiểm tra nhanh: tưởng tượng đưa widget vào trang admin rồi vào portal khách hàng. Nếu cả hai đều chạy chỉ bằng thay props và xử lý sự kiện, bạn đã an toàn.
Ví dụ thực tế: thêm timeline vào dashboard
Đội hỗ trợ thường muốn một màn hình kể câu chuyện của ticket: thay đổi trạng thái, ghi chú nội bộ, phản hồi khách, và sự kiện thanh toán hoặc giao hàng. Một timeline phù hợp, nhưng bạn không muốn sửa file sinh và mất việc khi tái tạo.
Cách an toàn là giữ widget tách biệt ngoài UI sinh và nhét nó vào trang bằng một wrapper mỏng.
Hợp đồng widget
Giữ đơn giản và dễ đoán. Ví dụ, wrapper truyền:
ticketId(string)range(7 ngày qua, 30 ngày qua, tuỳ chỉnh)mode(compact vs detailed)
Widget phát:
selectkhi người dùng nhấn một eventchangeFilterskhi người dùng thay range hoặc mode
Bây giờ widget không biết gì về dashboard, mô hình dữ liệu hay cách gọi request. Nó chỉ render timeline và báo hành động người dùng.
Wrapper kết nối với trang như thế nào
Wrapper nằm cạnh dashboard và chuyển dữ liệu trang thành hợp đồng. Nó đọc ticket ID hiện tại từ state trang, chuyển filter UI thành range, và map record backend thành định dạng event widget mong đợi.
Khi widget phát select, wrapper có thể mở panel chi tiết hoặc kích hoạt hành động của trang. Khi phát changeFilters, wrapper cập nhật filter trang và refresh dữ liệu.
Khi dashboard được tái tạo, widget vẫn nguyên vì nó sống ngoài file sinh. Thường bạn chỉ cần sửa wrapper nếu trang đổi tên field hoặc thay cách lưu filter.
Thói quen test và phát hành ngăn bất ngờ
Component tùy chỉnh thường hỏng theo cách nhàm chán: hình dạng prop thay đổi, event không còn phát, hoặc tái render trang nhiều hơn widget mong đợi. Một vài thói quen bắt những lỗi này sớm.
Test cục bộ: phát hiện lỗi ranh giới sớm
Coi biên giữa UI sinh và widget như một API. Test widget mà không cần app đầy đủ, dùng props cứng khớp với hợp đồng.
Render nó với props "điều kiện tốt nhất" và với giá trị thiếu. Mô phỏng các sự kiện chính (save, cancel, select) và xác nhận parent xử lý. Test dữ liệu chậm và màn hình nhỏ. Xác minh nó không ghi vào state toàn cục trừ khi đó là hợp đồng.
Nếu bạn xây trên app AppMaster Vue3, chạy các kiểm tra này trước khi tái tạo gì cả. Dễ chẩn đoán khi bạn không thay hai thứ cùng lúc.
Kiểm tra hồi quy sau tái tạo: xem lại điểm chạm đầu tiên
Sau mỗi lần tái tạo, kiểm tra lại điểm chạm: có cùng props được truyền không, và cùng events được xử lý không? Đó là nơi lỗi thường xuất hiện đầu tiên.
Giữ cách bao gồm predictable. Tránh import mong manh phụ thuộc vào đường dẫn file có thể di chuyển. Dùng một entry point ổn định cho component tùy chỉnh của bạn.
Với môi trường production, thêm logging nhẹ và bắt lỗi trong widget:
- Mounted với props chính (đã sanitize)
- Vi phạm hợp đồng (prop bắt buộc thiếu, sai type)
- Lỗi API với mã lỗi ngắn
- Trạng thái rỗng không mong đợi
Khi có lỗi, bạn muốn nhanh chóng trả lời: tái tạo có thay đổi inputs, hay widget đã thay đổi?
Bước tiếp theo: làm cho mẫu có thể lặp lại trong toàn app
Khi widget đầu tiên chạy ổn, lợi ích thực sự là làm cho nó có thể lặp lại để widget tiếp theo không là hack một lần.
Tạo một tiêu chuẩn nội bộ nhỏ cho hợp đồng widget và ghi lại nơi nhóm lưu ghi chú. Giữ ngắn gọn: đặt tên, props bắt buộc vs tùy chọn, tập sự kiện, hành vi lỗi và sở hữu rõ ràng (cái gì trong generated UI vs thư mục tùy chỉnh).
Cũng viết quy tắc biên bằng ngôn ngữ đơn giản: không sửa file sinh ra, giữ mã tùy chỉnh cô lập, và truyền dữ liệu chỉ qua props và events. Điều này ngăn "sửa nhanh" trở thành chi phí bảo trì lâu dài.
Trước khi xây widget thứ hai, chạy một thử nghiệm tái tạo nhỏ. Phát widget đầu tiên, rồi tái tạo ít nhất hai lần trong các thay đổi bình thường (thay label, thay layout, thêm field) và xác nhận không có gì vỡ.
Nếu dùng AppMaster, thường tốt nhất là giữ hầu hết UI và logic trong trình dựng trực quan (UI builders, Business Process Editor và Data Designer). Dành component Vue tùy chỉnh cho những widget thực sự độc đáo mà trình dựng không thể biểu đạt, như timeline chuyên biệt, tương tác biểu đồ, hoặc các điều khiển nhập phức tạp. Nếu muốn một điểm khởi đầu sạch cho cách tiếp cận này, AppMaster trên appmaster.io được thiết kế quanh ý tưởng tái tạo, vậy nên giữ widget tách biệt sẽ trở thành một phần tự nhiên của quy trình.
Câu hỏi thường gặp
Sửa file Vue được sinh ra rất rủi ro vì lần tái tạo tiếp theo có thể ghi đè hoàn toàn. Dù thay đổi của bạn tồn tại một lần, một sửa nhỏ trong trình dựng trực quan có thể tái tạo các template và xóa các tinh chỉnh thủ công.
Đặt toàn bộ mã Vue tự viết trong một thư mục riêng mà bạn quản lý (ví dụ custom hoặc extensions) và import nó như một phụ thuộc. Xem các trang sinh ra như đầu ra chỉ đọc và chỉ kết nối với component của bạn qua một giao diện nhỏ, ổn định.
Wrapper là một component mỏng bạn sở hữu ngồi giữa trang sinh ra và widget của bạn. Nó chuyển đổi hình dạng dữ liệu của trang thành props sạch và biến các sự kiện của widget thành hành động của trang, nên nếu dữ liệu sinh ra thay đổi sau này bạn thường chỉ cần cập nhật wrapper.
Giữ hợp đồng nhỏ: vài props cho dữ liệu widget cần, và vài sự kiện để báo ý định người dùng. Ưu tiên giá trị đơn giản và object bạn kiểm soát, thêm giá trị mặc định, kiểm tra đầu vào và thất bại nhẹ nhàng bằng trạng thái rỗng thay vì ném lỗi.
update:modelValue phù hợp khi widget hoạt động như điều khiển form và cần hỗ trợ v-model. change phù hợp cho hành động đã được xác nhận, ví dụ khi người dùng nhấn Lưu hoặc hoàn tất chọn, để parent không xử lý mỗi lần gõ phím.
Scope style của widget và dùng tiền tố class rõ ràng để các trang sinh ra không vô tình ghi đè CSS của bạn. Nếu cần theo theme ứng dụng, truyền token theme như props (màu, khoảng cách, cỡ chữ) thay vì import hoặc dựa vào style của trang.
Theo mặc định, để quy tắc nghiệp vụ bên ngoài widget. Hãy để trang hoặc backend quyết định quyền, quy tắc xác thực và hành vi lưu, còn widget tập trung vào hiển thị và tương tác, phát các sự kiện như select, retry, hoặc approve.
Tránh phụ thuộc vào các chi tiết không ổn định như đường dẫn file sinh ra, cấu trúc DOM của cha hoặc hình dạng object trạng thái nội bộ. Nếu cần, che giấu chúng trong một wrapper để việc đổi tên như user.name thành user.profile.name không bắt bạn viết lại widget.
Kiểm tra widget cô lập với các props cứng phù hợp hợp đồng, bao gồm giá trị hợp lệ và thiếu. Sau đó tái tạo và xác minh hai điều: cùng props vẫn được truyền, và cùng events vẫn được xử lý. Đó là nơi lỗi thường xuất hiện đầu tiên.
Không phải mọi UI đều cần mã tùy chỉnh; dùng component tùy chỉnh cho những thứ trình dựng trực quan không thể hiện tốt, như biểu đồ phức tạp, bộ chọn bản đồ, bảng ký chữ kí, hoặc bộ kéo-thả. Nếu yêu cầu có thể xử lý bằng UI và quy trình sinh ra, sẽ dễ bảo trì hơn về lâu dài.


