Cắm đầu cắm cổ dọn dẹp deadline và dọn nhà trước Tết nên ngâm bài blog lên men luôn rồi. Xin lỗi mọi người nhé. Nay rảnh rỗi có chút thời gian tranh thủ viết lách một tí kẻo sếp biết lại giao thêm task.
Nếu bạn chưa đọc tập trước, bạn có thể đọc tại đây.
Cross-platform Apps sẽ được viết tắt bằng CPA để thuận tiện cho người viết bài
Sau khi nhận ra vấn đề về license (tạm dịch là giấy phép sử dụng) liên quan tới Shopify Polaris, team quyết định tạm thời ngừng phát triển tính năng mới để giải quyết vấn đề trên. Đây cũng là một cơ hội để team có thể nhìn lại và đánh giá các công nghệ đã chọn.
Frontend Du Ký tập này sẽ kể về hành trình đi tìm những công nghệ mới, phù hợp hơn với mục tiêu sắp tới của team. Tiêu chí lựa chọn công nghệ sẽ bao gồm:
Nguồn ảnh: https://dribbble.com/shots/15135572-Scheduling-Master
Thoạt đầu team chỉ định dành ra 2 đến 3 tuần để đổi component kit rồi sau đó sẽ tiếp tục phát triển tiếp trên nền tảng đã có sẵn. Tuy nhiên, cá nhân mình thấy nền tảng hiện tại chưa đủ "vững" để làm nền móng cho các ứng dụng kế tiếp. Có lẽ một phần cũng do định hướng ban đầu của Cross-Platform Apps (CPA) là một dự án thử nghiệm nên team thật sự chưa chú trọng tới việc lựa chọn công nghệ, dẫn đến việc gặp vấn đề về giấy phép như mình đã đề cập.
Ngoài ra, sau một thời gian dài phát triển thì công nghệ cũng đã bị lỗi thời, nếu tiếp tục sử dụng sẽ mất đi nhiều tính năng cũng như bản vá lỗi mới của thư viện/framework đang sử dụng.
Sau khi thảo luận, team quyết định giao phó toàn bộ trọng trách trên lên đôi vai bé nhỏ của mình. Thôi thì cũng được. Anh/chị/em đùn đẩy trách nhiệm tin tưởng nên mình cũng cố gắng hết sức để không phụ lòng mọi người.
Cùng bắt đầu tìm quá trình tìm hiểu và thử nghiệm của mình nha!
Team đã rất nhiều lần đắn đo về việc có nên sử dụng TypeScript (TS) ở phía frontend hay không và hầu hết câu trả lời đều là không. Quyết định này hoàn toàn có cơ sở, bạn có thể xem lần tập trước để hiểu lý do vì sao team vẫn sử dụng JavaScript (JS) để phát triển. Tuy nhiên, với cấu trúc dữ liệu của ứng dụng ngày càng nhiều và phức tạp thì việc sử dụng TS là một lựa chọn hợp lý trong thời điểm này.
Đây là một quyết định tương đối "táo bạo" vì bản thân mình và team chưa có nhiều kinh nghiệm trong việc sử dụng TS, đặc biệt là cách khai báo type
(kiểu dữ liệu, ví dụ: string
, number
, { foo: string, bar: boolean }
) – một trong những yếu tố quyết định mức độ hiệu quả của TS. Dù vậy, mình vẫn muốn "thử sai" để lấy kinh nghiệm cho các dự án sắp tới. Chẳng mấy khi có được cơ hội thử sai, ngại gì mà không thử phải không các bạn?
Kế hoạch ban đầu của mình là chuyển dần các file JS hiện có trong dự án qua TS. Tuy nhiên, sau khoảng 2 tiếng khai báo kiểu, đổi từ file này qua file khác cũng như sửa hàng trăm lỗi xuất hiện sau khi đổi tên file thì mình quyết định viết lại từ đầu. Lý do:
strict: true
. Để tận dụng tối đa hiệu năng của TS thì mình quyết định bật strict mode ngay từ ban đầu. Việc bật strict mode sẽ gây khó khăn trong quá trình chuyển đổi nhưng nếu quyết định viết lại từ đầu thì mình tin rằng đây là một quyết định hợp lýcomponents
, services
, config
, utils
thì mình còn tạo thêm: hooks
: chứa các custom React hook dùng chung, chủ yếu là các hook liên quan tới việc fetch (tải/lấy) dữ liệubusiness
: chứa các component đặc biệt chỉ dùng cho một trang cụ thể. Bên trong business
sẽ phân chia thư mục con theo tên ứng dụng, ví dụ: /business/ega-shop/
stores
: chứa khai báo global state (mình gọi là store để giống cách gọi của thư viện zustand đang dùng)Dù đã triển khai theo hướng viết lại nhưng TS vẫn gây khá nhiều khó khăn cho mình trong lúc phát triển. Một trong những vấn đề mình gặp phải là việc phải lặp lại condition (điều kiện kiểm tra if/else) để "thỏa mãn" compiler của TS. Cụ thể như sau:
// Code chạy đúng nếu dùng trực tiếp 'condition'
function createMessage(str: string | null) {
if (typeof str === 'string' && str.trim().length > 0) {
return str.concat(' is a valid string') // ok
}
}
// TS báo lỗi khi tách riêng 'condition' thành biến
function createMessage(str: string | null) {
const isValidString = typeof str === 'string' && str.trim().length > 0
if (isValidString) {
return str.concat(' is a valid string') // error: Type of 'str' variable might be null
}
}
Rất may là ở phiên bản TS 4.4 thì vấn đề này đã được khắc phục. Các bạn có thể đọc thêm tại đây.
Một vấn đề khác mình cũng gặp phải trong quá trình làm việc với TS là cảm giác "an toàn" giả do TS mang lại. Công tâm mà nói thì thực sự đây là sơ xuất của bản thân mình. Tuy nhiên, mình vẫn muốn chia sẻ điều này tới các bạn để các bạn có nhận thức về nó và tránh nó trong dự án của các bạn.
Vấn đề xuất hiện khi mình làm việc với dữ liệu của một bên đối tác. Dữ liệu của họ như sau:
{
...,
price: "100000" // dữ liệu là chữ (string)
}
Vì một lý do nào đó, mình đã khai báo kiểu như sau:
{
...,
price: number // khai báo nhầm kiểu số (number)
}
Như các bạn có thể thấy là mình đã khai báo sai kiểu dữ liệu cho thuộc tính price
. TS sẽ không báo lỗi cho chúng ta ở trường hợp này vì về lý thuyết thì code không hề sai. Bản thân mình cũng khá chủ quan, không kiểm tra lại type của biến price
khi sử dụng, dẫn đến các phép tính toán cộng đều bị sai (phép cộng sẽ được hiểu là ghép chuỗi).
Trong cái rủi có cái may. Vì dự án vẫn đang trong quá trình phát triển nên sự cố trên không gây tổn hại tới khách hàng. Dù vậy, đây cũng là hồi chuông cảnh tỉnh để mình để tâm hơn đến việc kiểm tra kiểu của dữ liệu, đặc biệt là đối với dữ liệu bên ngoài.
Việc thay đổi ngôn ngữ qua TypeScript trong lần chuyển đổi này thực chất cũng như câu tục ngữ "thừa nước đục thả câu" mà thôi. Sẵn tiện chuyển đổi thì làm luôn phải không các bạn?
Tâm điểm chính của lần chuyển đổi này vẫn là UI component, cụ thể hơn là việc thay thế Shopify Polaris bằng một UI component kit khác. Vì đã từng trải nghiệm và áp dụng sơ AntDesign trong một dự án outsource ở công ty rồi nên mình quyết định chọn AntDesign để thay thế Shopify Polaris.
Một số ưu điểm của AntDesign:
Sau khi được "thuyết phục" bởi những điều trên, mình bắt đầu chuyển đổi toàn bộ giao diện của CPA. Quá trình chuyển đổi cũng khá đơn giản, thậm chí là đơn giản hơn so với khi sử dụng Shopify Polaris vì lượng component phong phú của AntDesign, mình không cần phải tạo custom component nào cả. Khó khăn lớn nhất lúc bấy giờ có lẽ là việc chuyển đổi form. Mình phải chuyển phần state cũ – quản lý bằng tay qua hệ thống quản lý form của AntDesign. Chính tại đây thì mình phát hiện ra một số điểm yếu của AntDesign mà mình không nhận ra trước đó
// - isEnabled: switch on/off
// - optionalField: ẩn/hiện tùy thuộc vào trạng thái của 'isEnabled'
// Shopify Polaris (form state tự quản)
{
formValues.isEnabled && (
<Input value={value} onChange={handleFieldChange('optionalField')} />
)
}
// AntDesign (form state có sẵn)
<Form.Item
noStyle
shouldUpdate={(prevValues, curValues) =>
prevValues.isEnabled !== curValues.isEnabled
}
>
{({ getFieldValue }) => {
if (!getFieldValue('isEnabled')) {
return null
}
return (
<Form.Item label="Optional field" name="optionalField">
<Input />
</Form.Item>
)
}}
</Form.Item>
optionalField
sẽ bị xóa khi submit nếu isEnabled
có giá trị là false
. Điều này gây cản trở khá nhiều vì giao diện phía mình được chia thành nhiều tab, đồng nghĩa với việc khi submit thì chỉ submit được tab đang mở mà thôi. Để chạy được chỗ này thì mình phải "bùa chú" một chút bằng cách render luôn các trường ẩn và thêm thuộc tính display: none
. Tuy không ảnh hưởng quá nhiều tới hiệu năng của trang nhưng việc phải để tâm tới "tính năng" này khi phát triển UI khá là khó chịu và thật lòng mà nói thì không nên cóDù vậy, vì một lý do gì đấy thì mình vẫn tiếp tục sử dụng AntDesign để chuyển đổi hoàn chỉnh hệ thống CPA. Quá trình này mất khoảng một tuần. Sau khi bàn giao cho team tester thì mình lại xách balo lên và đi tìm một UI component kit khác phù hợp hơn. Nếu tiếp tục sử dụng AntDesign để phát triển thì mình tin là sẽ có rất nhiều vấn đề và giới hạn kỹ thuật không đáng có trong tương lai.
Một ngày bình thường như một ngày khác, khi dạo chơi trên Best Of JS thì mình bắt gặp ChakraUI. Sau khi xem sơ qua thì nhận định của mình như sau:
Nhận thấy ChakraUI còn thiếu sót khá nhiều, mình quyết định để nó qua một bên và tiếp tục tìm hiểu các công nghệ khác để phụ trợ. Các component còn thiếu mình và team hoàn toàn có thể bổ sung thêm. Tuy nhiên, mình vẫn muốn có một hệ thống quản lý form hoàn chỉnh để sử dụng. Hệ thống tự dựng hiện tại khá hạn chế và hiệu năng không cao đối với những form lớn.
Lúc bấy giờ có khá nhiều thư viện quản lý form. Mình có dùng qua một số thư viện thì cảm thấy API vẫn chưa phù hợp lắm. Ngay lúc định bỏ cuộc thì mình tìm thấy React Hook Form. Với một người "nghiện" sử dụng hook như mình thì React Hook Form chẳng khác gì tình yêu sét đánh. Đánh giá sơ bộ của mình về React Hook Form:
Rút kinh nghiệm ở lần trước, dù là tình yêu sét đánh đi chăng nữa thì mình vẫn muốn kiểm chứng lại để đảm bảo quá trình áp dụng vào dự án thật không gặp vấn đề gì cả. Và đúng như mình dự đoán, vẫn có một số vấn đề khi kết hợp ChakraUI và React Hook Form:
// Dữ liệu ban đầu
let todos = ['Tìm UI component kit', 'Tìm thư viện form management']
// => Render ra 2 dòng với tiêu đề như trên
// Mình thêm mới một todo
todos = [
'Tìm UI component kit',
'Tìm thư viện form management',
'Tích hợp ChakraUI và React Hook Form'
]
// => Render ra 3 dòng
// Mình đổi ý, tiến hành "Hủy thay đổi"
todos = ['Tìm UI component kit', 'Tìm thư viện form management']
// => Vẫn render ra 3 dòng vì state không được cập nhật
Cảm thấy hơi tiếc nuối cho một thư viện tốt như React Hook Form, mình quyết định lên phần Discussion của React Hook Form để nhờ hỗ trợ và vô tình trở thành "hỗ trợ viên" của React Hook Form luôn. Cũng nhờ vậy mà các vấn đề về xóa trường ẩn hoặc dữ liệu array cũng dần được xử lý. Mình thì có thêm một người bạn. Một công đôi việc phải không các bạn?
Sau khi ChakraUI và React Hook Form đã đủ "chín", mình tiến hành chuyển đổi dự án từ AntDesign qua ChakraUI. Quá trình này diễn ra khá nhanh và suôn sẻ, một phần vì đã có chuẩn bị kỹ hơn lần trước, một phần là vì lần này không cần phải can thiệp vào các file service, utils, … để chuyển qua TypeScript nữa nên công việc phải làm giảm đi đáng kể.
Sau khi đánh giá và "chốt đơn" combo ChakraUI và React Hook Form thì mình có thảo luận với team về việc áp dụng combo này cho các dự án khác nữa. Tất nhiên là team đồng ý làm rồi, nhưng team không muốn việc phải copy và paste code từ dự án này qua dự án khác. Hơn nữa, mỗi lần có thay đổi lại phải lặp lại quá trình đấy một lần nữa. Rủi mà quên nói nhau thì giao diện mỗi người một kiểu luôn.
Nhận ra vấn đề thì mình bắt đầu tìm hiểu về cách đăng tải (publish) một package riêng cho team sử dụng. Vì dự kiến package này chỉ lưu hành nội bộ nên mình ưu tiên private package hơn là public. Tất nhiên là ngoài việc tạo package thì vẫn có các dịch vụ khác hỗ trợ share code nhưng chi phí quá cao hoặc cách thiết lập quá phức tạp khiến mình bỏ qua các giải pháp đấy.
Tìm hiểu khoảng một ngày thì mình chọn ra được hai giải pháp sau:
Nói đến đây chắc các bạn cũng đoán được team chọn giải pháp nào rồi nhỉ. Trong lúc chờ team backend triển khai Verdaccio thì mình tìm hiểu về cách tạo một package như thế nào. Package này chủ yếu sẽ bổ sung các component còn thiếu của ChakraUI, ví dụ như DatePicker, ColorPicker, Rich-text Editor, … đồng thời tạo khung giao diện (layout) thường sử dụng để dev không cần phải tạo lại nữa. Điều này vừa tiết kiệm thời gian phát triển, vừa giúp đồng bộ giao diện giữa các dự án với nhau. Đây cũng là một trong những ấp ủ của anh chị em trong team bấy lâu nay, bây giờ mới có cơ hội biến nó thành hiện thực.
Ngoài việc thay đổi các công nghệ chính để xây dựng dự án thì mình cũng muốn tích hợp các công nghệ bổ trợ cho quá trình phát triển của team, cụ thể:
git add
). Mình dùng để chạy ESLint để check lỗi và Prettier để format lại codeMặc dù đôi lúc có hơi khó chịu khi cần sửa lỗi gấp mà vẫn phải chờ kiểm tra nhưng cũng nhờ vậy mà giảm đi đáng kể số lần phải sửa đi sửa lại lỗi trên production. Khách hàng của EGANY cũng hài lòng hơn về điều này.
Chặng đường chuyển đổi tới đây là kết thúc. Tất nhiên là còn nhiều thay đổi khác nữa trong quá trình thực hiện nhưng khó lòng nào nhớ hết được gói gọn được trong một bài blog nên xin phép bỏ qua nha mọi người.
Bản thân mình và team đạt được khá nhiều thành quả qua lần chuyển đổi này:
Hi vọng bài viết sẽ giúp ích được cho các bạn đã, đang và sẽ triển khai một dự án tương tự như CPA. Nếu không giúp ích được các bạn trong công việc thì hi vọng bạn đã có những giây phút đọc bài thật thoải mái. Nếu có bất kỳ thắc mắc hoặc góp ý nào, các bạn cứ comment ở bên dưới nha.
Cám ơn các bạn đã đọc bài. Happy hacking!
Nếu bạn thấy hành trình bọn mình thú vị và muốn cùng nhau viết tiếp thì tham khảo thông tin này nhé 😃
Trong phần trước, mình đã chia sẻ với mọi người những khó khăn khi team…
Xin chào, tiếp tục loạt bài về trải nghiệm của mình trong việc xây dựng…
Nếu bạn yêu thích phát triển sản phẩm với nhiều thử thách và cơ hội…
Trong phần 1, mình đã giải thích lý do tại sao EGANY chọn AWS làm…
Trong giới công nghệ hiện nay, Amazon Web Service không còn là một cái gì…
Xin chào! Chúng ta lại quay trở lại với loạt bài về trải nghiệm của…