Tiki đã dùng React Native như thế nào?

Tại Tiki, chúng tôi hiện đang phát triển các tính năng mới dành cho Android và iOS app, bằng React Native. Nếu bạn cũng có ý định dùng tới React Native cho project của mình, hay chỉ đơn giản là tò mò về nó – Hãy đọc bài viết này. Qua đây, tôi muốn chia sẻ kinh nghiệm của chúng tôi về RN cũng như bài học rút ra từ nó.

Vì sao lại chọn React Native?

Hot reloading & Live reloading

Chúng rất yêu thích khả năng live reloading trong React Native. Trong những ngôn ngữ lập trình khác, chúng ta phải chờ cho tới khi quá trình compile được hoàn tất, có khi lên tới hơn khoảng ~120 seconds mới xem được kết quả output. Nhưng React Native thì lại cho phép ta modify ứng dụng mà không cần phải recompile nó. JavaScript code được load từ local server trong giai đoạn lập trình, và được tích hợp vào app cùng với những resources khác. Nhờ đó mà giúp rút ngắn thời gian cho UI iterations.

Ngoài ra, React Native còn tích hợp thêm tính năng Hot Reloading. Nó giúp giữ cho app hoạt động bình thường và update/thêm những thay đổi cùng lúc trong thời gian thực. Hiện tại, hot reloading vẫn không thực sự hoạt động hoàn hảo với một số tình huống đặc biệt nhưng với một lần reload lại app thường sẽ giúp fix việc này.

Reusability

Một tính năng tuyệt vời khác của React Native chính là những dòng code chung vốn có thể xài cho cả iOS, và Android platforms. Một ví dụ điển hình như khi bạn dùng React Native’s switch component, nó sẽ render UISwitch trong iOS và Native Switch component trong Android. Với các ứng dụng của Tiki, chúng tôi có tới 80% JavaScript code được dùng cho cả Android và iOS.

Incremental adopting React Native

Chúng tôi bắt đầu dùng nó vào đầu 2016 để phát triển cho các tính năng mới như chọn ra một đặc tính để tìm kiếm sản phẩm.

Do việc chuyển đổi hoàn toàn qua React Native sẽ rất tốn kém và một số tính năng cũng còn cần đến ngôn ngữ lập trình native. Thế nên chúng tôi quyết định hướng đi hybrid app.

Tại sự kiện thường niên của Tiki, gọi là “Dzut Co Hon”, các trang web của công ty sẽ thể hiện thông điệp tri âm tới các khách hàng. Tuy nhiên, tháng 7 vừa rồi, thì sự kiện còn được đưa lên mobile platform. Để tôi miêu tả ngắn gọn về sự kiện này thì cứ vào thời điểm tháng 7 âm lịch, với các quốc gia như Vietnam, China, HongKong, mọi người sẽ cúng đồ vật để và cho đi thức ăn cũng như tiền bạc, với hi vọng cô hồn sẽ không quấy phá công việc và cuộc sống của mình. Còn với Tiki, chúng tôi hạ giá tới 80% nhân dịp trên. Tất cả những gì users cần làm là “tap” và chọn món hàng mình muốn mua, người nhanh nhất sẽ thắng cuộc.

Bởi vì lượng người dùng lên đến 60k trong thời điểm trên, các sự kiện đã được lên lịch trước và không thể thay đổi.

Tuyển dụng lập trình React Native

Các vấn đề chúng tôi gặp phải

Các bản cập nhật được tung ra quá nhiều

React Native vẫn trong giai đoạn phát triển. Có rất nhiều tính năng, và bug fixed trong mỗi bản release. Tuy vậy chúng cũng chứa những thay đổi lớn khiến cho mỗi lần update là app rất dễ bị crash. Trong những trường hợp trên, do có những tính năng đang xài React Native vì thế mà phải bỏ ra rất nhiều thời gian để khắc phục chúng mỗi khi update phiên bản mới.

Page transition vẫn còn chậm

Sau khi nhấn vào nút sign-in, màn hình sẽ hiện ra thời gian và các sản phẩm có trong sự kiện

Khi chúng tôi đưa route đến một navigator, JavaScript sẽ phải render toàn bộ components cần thiết cho screen mới này. Ngoài ra nó còn phải thực hiện page transition cũng ngay trong JS thread đó. Do phải thực hiện cả hai nên việc chuyển đổi giữa các page bị chậm đi. Do đó mà chúng tôi phải lên lịch cho từng hoạt động sử dụng InteractionManager.runAfterInteractions(). Nhờ đó, trước khi transition được diễn ra, tất cả tab-data sẽ được pre-loaded cùng với lists các sản phẩm trong Register Screen . Sau khi transition hoàn thành, ta sẽ chỉ cần tập trung vào render nội dung. Nhờ đó mà trải nghiệm của người dùng trở nên mượt mà hơn.

Tab Layout

Tại Tiki, chúng tôi hiện đang sử dụng một trong những libraries nổi tiếng nhất là react-native-scrollable-tab-view. Vốn là pure javascript implementation. Cách thức hoạt động khá giống với material design pattern với các props ở rộng nhằm có thể điều khiển một cách dễ dàng. Tuy vậy, library này bắt đầu để lộ ra vấn đề về hiệu năng bởi sự phức tạp trong UI của Tiki. Cũng bởi vì là pure JS implementation, nên xuất hiện độ trễ giữa thông tin từ user tới ứng dụng. Do đó, chúng tôi quyết định viết ra phiên bản native RNTabLayout của riêng mình và port nó lên Javascript.

Code cho riêng từng platform

Khi team QA test tính năng countdown, Tiki phát hiện ra một bug khá lạ. iOS đếm nhanh hơn Android. Chúng tôi thử khắc phục bằng thử nghiệm sau

let currentTime1
countDown(10000)
let currentTime2

Kết quả mong đợi là currentTime2 — currentTime1 = 10000 trên cả 2 platform. Thế nhưng chỉ có iOS là ra đúng. Hơn nữa, iOS render list sản phẩm trên nhanh hơn Andoid. Do đó, chúng tôi đã áp dụng một vài mánh khóe

getTimeRemaining(endTimeInMillis) {    
  const platformDelta = Platform.OS == 'ios' ? 1000 : 1600;    
  let diffInMillis = new Date(endTimeInMillis).getTime() -             Date.now() - platformDelta;
...
}

Vấn đề hiệu năng

“Dzut Co Hon” vốn dựa trên Redux. Chúng tôi dự định là khi click vào sản phẩm, màn sẽ ngay lập tức hiển thị tiến độ cũng như thay đổi state ngay khi network call. Tuy vậy, thực tế thì nó sẽ mất một giây hoặc hơn khi có nhiều sản phẩm được chọn. Sau khi điều tra, chúng tôi nhận ra rằng `Reducer` mất rất nhiều thời gian để nhận nhiều dispatched actions  từ ProductComponent. Vì thế Tiki quyết định dùng `setState()` trong `ProductComponent` và bỏ Redux ra khỏi `ProductListComponent`.

Với các app mới của Tiki vốn sử dụng RN 0.42, ListView vẫn gặp vấn đề do sự phức tạp với nhiều view types và tabs.

Bạn có thể thấy nó thật khó chịu khi scrolling. Do đó chúng tôi sẽ convert qua `FlatList` để xem nó như thế nào.

Đối với nhiều tabs với `ViewPager`, khi các tab được loaded, tất cả `ListView` và toàn bộ `Store` sẽ được lưu trữ. `OutOfMemory` thường diễn ra với các thiết bị low-end. Vì thế chúng tôi phải chỉ dữ lại data cần cho những tab đang hoạt động trong `Store`. Data cho các tabs khác sẽ được đưa vào `LocalStorage`. Nói cách khác, những tabs đó sẽ chỉ render duy nhất loading view…

Và khi user trở về các tab đó, thì chúng ta chỉ mất thời gian lấy data từ disk cache và render nó.  

`shouldComponentUpdate()` và `PureComponent` thật sự rất tiện lợi. Khi users slide một banner hay deal thì indicator sẽ được thay đổi. Như vậy, các component “mẹ” hoặc là cả component tree cũng sẽ thay đổi theo và khiến cho hiệu năng bị giảm sút. Đo đó chúng tôi dùng `shouldComponentShouldUpdate()` để bảo đảm các component không liên quan thì không bị thay đổi.

Integration liên tục

Tại Tiki, chúng tôi dùng Jenkins để deploy native apps. Trong khi đó, quá trình deploy React Native cũng muốn áp dụng cách thức như vậy. Với mỗi commit được nhập vào với nhánh staging, test sẽ được chạy và Jenkins sẽ đẩy JS bundle mới nhất lên CodePush server, rồi thông báo cho chúng tôi bằng Slack channel.

Dựa trên một sample, chúng tôi tạo ra một custom CLI tool để chạy `code-push release-react “TikiHome-ios” ios -d “Staging” — des “Jenkins deploy” — dev true -m false -t`. Sau khi nhánh dev thông qua thì code sẽ được xác nhập vào nhánh chính – master branch. Jenkins sẽ chạy tất cả các tests, tạo production target, deploy bundle mới nhất, và cả tạo ra một tag version.

Lời kết

Chúng tôi tin rằng React Native là một framework vô cùng tuyệt vời. Mặc dù nó vẫn còn vài vấn đề, nhưng chúng hoàn toàn bị che lấp bởi vô số các lợi ích mà React Native mang lại. Tiki hoàn toàn hài lòng với lựa chọn của mình – Tất những quyết định trên đã giúp chúng tôi rút ngắn thời gian đi rất nhiều cho việc phát triển các sản phẩm mobile.

Nguồn: topdev.vn via Cuong Le via CodeCamp