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.
Giới thiệu
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:
- Ưu tiên license MIT hoặc các license tương tự, cho phép sử dụng trong các dự án thương mại (commercial project)
- Đảm bảo đáp ứng được các yêu cầu về tính năng hiện có cũng như các tính năng dự định sẽ phát triển
- Dễ sử dụng, dễ tiếp cận để giảm thiểu tối đa thời gian cần để chuyển đổi
Kế hoạch triển khai
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!
TypeScript
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ý- Tổ chức lại cấu trúc thư mục. Việc viết lại từ đầu cũng giúp mình hệ thống hóa lại các file hiện có và chuẩn hóa quy tắc đặt tên file. Ngoài các thư mục thường thấy như
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 trongbusiness
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ễ quản lý git và deploy. Để vừa không đụng chạm dự án cũ đang chạy (phục vụ cho tester) vừa dễ dàng quản lý cho team dev thì việc tách riêng một repository (repo) mới và viết lại từ đầu là lựa chọn đúng đắn. Khi có 2 repo riêng biệt thì việc copy code qua lại cũng khá đơn giản
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.
Ant Design
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:
- Component phong phú. Danh sách component của AntDesign khá đồ sộ và đa dạng, đáp ứng đủ và thậm chí là "dư dả" cho những dự định tương lai của team
- Hệ thống form chuyên nghiệp. Mình khá ưng ý với hệ thống form của AntDesign. Ngoài việc hỗ trợ về giao diện thì form còn hỗ trợ về việc quản lý dữ liệu trong form và hỗ trợ luôn các loại xác thực dữ liệu (validation) cơ bản cũng như nâng cao. Không còn cảnh phải tự tổ chức dữ liệu bằng state hay reducer như trước nữa, mình chỉ cần gắn event handler là đã có thể thu thập dữ liệu của hàng chục, hàng trăm thông tin có trong form rồi
- Có tập đoàn lớn "chống lưng". AntDesign là một sản phẩm của tập đoàn Ant nên việc một ngày nào đó AntDesign biến mất cũng khá khó xảy ra. Việc này cũng khiến mình yên tâm hơn trong quá trình phát triển lâu dài, ít nhất là trong 2 tới 3 năm tiếp theo
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 đó
- Cú pháp (syntax) rườm rà. Nếu như form của bạn đơn giản, AntDesign đáp ứng rất tốt ở điểm này. Tuy nhiên, nếu form của bạn phức tạp, các trường (field) trong form thay đổi dựa trên dữ liệu người dùng nhập vào thì code phát sinh phình ra rất nhiều. Ví dụ đơn giản:
// - 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>
- Chỉ nhận giá trị trường đang hiển thị. Mặc định AntDesign sẽ xóa thông tin các trường không được render trên màn hình. Ví dụ như trường hợp trên thì trường
optionalField
sẽ bị xóa khi submit nếuisEnabled
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ínhdisplay: 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ó - Khó tùy chỉnh theme. Để phát triển hình ảnh công ty thì team mình có nhu cầu tùy chỉnh theme của component (màu sắc, font, …). Tuy nhiên, AntDesign khá hạn chế về việc này. Nếu mình muốn tùy chỉnh theme thì phải cài đặt thêm các thư viện khác để hỗ trợ build với LESS. Ngoài ra thì việc chuyển đổi giữa light/dark theme cũng phải chọn ngay từ lúc build, khá gò bó ở điểm này
- Đa dạng ngôn ngữ. Có lẽ các bạn thắc mắc tại sao đây lại là một khó khăn phải không? Đa dạng ngôn ngữ ở đây không có nghĩa là hỗ trợ nhiều ngôn ngữ khác nhau mà có nghĩa là sử dụng cùng lúc nhiều ngôn ngữ, cụ thể là tiếng Trung và tiếng Anh trong tài liệu cũng như trên cộng đồng ở GitHub. Thỉnh thoảng các bạn sẽ thấy một số từ tiếng Trung xuất hiện trong tài liệu và thường xuyên thấy các issue trên GitHub viết hoàn toàn bằng tiếng Trung. Rất có thể đó là lỗi bạn đang tìm kiếm nhưng vì không hiểu tiếng Trung nên bạn sẽ không thể tiếp cận được giải pháp cho vấn đề đấy. Google Translate cũng khó có thể cứu bạn trong trường hợp này vì tiếng Trung theo mình đánh giá là một ngôn ngữ phức tạp, chưa kể còn có nhiều dạng khác nhau nên dịch không đúng ý người viết đượ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.
ChakraUI + React-hook-form
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:
- Cơ bản, không nhiều component. So với AntDesign thì lượng component của ChakraUI ít hơn hẳn và không đa dạng bằng
- Tailwind ở dạng component. Từng làm việc với các thư viện functional CSS nên mình cũng khá thích việc ChakraUI hỗ trợ API dựa trên Tailwind. Có lẽ đây cũng là một trong những lý do vì sao ChakraUI thiết kế khá đơn giản vì nếu thiết kế càng phức tạp thì người sử dụng (developer) càng phải custom nhiều
- Tùy chỉnh theme dễ dàng. Việc tùy chỉnh theme cho ChakraUI khá đơn giản, bạn chỉ cần khai báo thông số theme vào file config là xong rồi. Một điểm cộng nữa của ChakraUI là hỗ trợ light/dark theme ngay từ đầu và có sẵn script để hiển thị nút chuyển chế độ nếu bạn có nhu cầu
- Không có hỗ trợ quản lý form. ChakraUI chỉ tập trung xử lý về UI nên không có hệ thống quản lý form như AntDesign. Tuy vậy, ChakraUI hỗ trợ sẵn cho các nhu cầu báo lỗi, báo trường ràng buộc, … rất tốt nên bạn có thể dễ dàng tích hợp form state có sẵn hoặc một thư viện quản lý form bên ngoài mà không gặp phải khó khăn gì cả
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:
- Hook based. Hiểu một cách khác thì thay vì quản lý form dựa trên component thì React Hook Form sử dụng hook. Về bản chất thì cũng không có khác biệt gì quá lớn về hiệu năng nhưng mình thích sử dụng hook hơn, code mình "sạch" hơn một tí so với sử dụng component. Đây hoàn toàn là đánh giá chủ quan
- Hiệu năng cao. So với các thư viện hiện có trên thị trường thì React Hook Form có hiệu năng vượt trội hơn hẳn khi sử dụng uncontrolled input. Về controlled component thì mình không thấy đề cập nhưng qua quá trình trải nghiệm thì thấy cũng khá ổn
- Dễ tích hợp. API của React Hook Form khá ít, đơn giản và tương đối dễ dùng. Mình chỉ tốn khoảng 10 phút để làm quen và xử lý các dạng form phức tạp hiện có trong dự án
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:
- Không submit trường ẩn hoặc disabled. Để các bạn không hiểu sai thì trường ẩn ở đây là trường không được render. Điểm yếu này cũng giống như AntDesign. Ngoài ra thì React Hook Form sẽ tự loại bỏ các trường bị disabled luôn. Đây là hành vi bình thường của HTML, không có gì là sai cả nên mình cũng không quá quan ngại về nó. Chỉ cần đổi disabled thành readonly là giải quyết được rồi
- Dữ liệu array mặc định là uncontrolled. Vì lý do hiệu năng nên React Hook Form mặc định xem dữ liệu array là uncontrolled. Việc này ảnh hưởng kha khá tới quá trình xử lý giao diện của phía mình, cụ thể là trong tính năng "Hủy thay đổi" (revert changes). Vì dữ liệu là uncontrolled nên khi revert sẽ không thay đổi theo state mới nhất. Để mình ví dụ cụ thể cho các bạn dễ hiểu hơn nha:
// 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ể.
Private package với Verdaccio
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:
- NPM private package. Private package được hỗ trợ sẵn bởi NPM. Đơn giản, dễ dùng, không cần thiết lập gì nhiều. Tuy nhiên thì chi phí sử dụng khá cao so với team. Giá tại thời điểm đó là $7 cho một thành viên. Nếu
keo kiệttiết kiệm chỉ cấp tài khoản cho leader dùng thì cũng mất $7 x 2 = $14 cho một tháng rồi - Verdaccio. Đây là một giải pháp private package tự host, tích hợp được với NPM. Tìm hiểu sâu hơn thì Verdaccio có những giới hạn nhất định so với NPM "xịn" nhưng về lý thuyết thì vẫn đáp ứng đủ nhu cầu cho team. Hơn nữa, để tự host Verdaccio thì không cần tốn quá nhiều, chỉ cần một cụm VPS giá $5 là đã có thể chạy ngon lành rồi
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.
Công nghệ bổ trợ
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ể:
- Husky. Thư viện hỗ trợ chạy lệnh thông qua git hook (commit, push, …). Mình dùng để trigger các lệnh cần thiết để kiểm tra chất lượng code cũng như đảm bảo rằng không có lỗi hoặc ít lỗi xảy ra khi triển khai lên môi trường production
- Lint-staged. Thư viện hỗ trợ chạy lệnh cho các file được staged (dùng lệnh
git add
). Mình dùng để chạy ESLint để check lỗi và Prettier để format lại code - ESLint. Thư viện hỗ trợ kiểm tra lỗi cũng như các best practice nên có trong dự án. Những lỗi ngớ ngẩn như gõ sai tên biến, trùng tên biến hoặc khai báo thiếu dependencies (biến phụ thuộc) khi sử dụng hook đều được "bắt" và ngăn chặn kịp thời trước khi commit
- Prettier. Thư viện hỗ trợ format code. Theo mình quan sát thì kiểu cách (style) code mỗi người sẽ khác nhau. Khi commit thì xuất hiện nhiều style trong một file khiến việc đọc code đôi lúc hơi khó khăn. Mình thiết lập prettier để giải quyết vấn đề này. Khi code dev có thể format theo sở thích cá nhân của mình nhưng khi commit thì code sẽ được format về một định dạng chung, tạo sự đồng nhất giữa các thành viên trong team
Mặ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.
Tạm kết
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:
- Giải quyết vấn đề về giấy phép
- Tiếp xúc nhiều công nghệ mới
- Biết cách thử nghiệm, áp dụng công nghệ sao cho hiệu quả nhất
- Có được package chính thức đầu tiên tại EGANY
- Có khung sườn dựng app chỉnh chu, chuyên nghiệp
- Rút ngắn thời gian triển khai dự án mới
- Một chút "danh vọng" khi tham gia hỗ trợ thư viện React Hook Form
Nội dung để viết blog
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é 😃