(React Native) Trà chanh gió mùa về kiến trúc mới

Flutter phi kiệu bay trong gió, React Native cưỡi chó chạy theo sau... - Câu đùa khi so sánh về hiệu năng ( Native performance) giữa hai thanh niên này.
Nhưng với quả bom sắp tới của Facebook team, thì liệu điều đó còn có đúng?

Cuộc chiến chưa thấy hồi kết giữa Flutter và React Native

Được debut bởi Facebook lần đầu vào 26 tháng 3 năm 2015, idol RN nổi lên như một hiện tượng với lời hứa ngọt ngào làm 1 ăn 2, không làm mà vẫn có ăn dễ code dễ bảo trì khiến các con dân dev ghét xamarin và các sếp muốn tiết kiệm túi tiền của mình như vớ được mùa. Hàng loạt job mới được bắn ra, cộng đồng slave tăng chóng mặt,... và giờ đây hắn đã yên vị trên ngai vàng của những Cross Platform Mobile.

Flutter ra mắt dân chúng vào tháng 5 năm 2017 và được chống lưng bởi sugar daddy Google. Flutter sử dụng Dart - được gọi là bản nâng cấp của Javascript, cũng như một loạt ưu điểm so vởi RN, mà cái thường được dân tình quan tâm nhất: Hiệu năng vượt trội. Nhờ thế, chẳng mấy chốc mà Flutter đã dần bắt kịp senpai của mình dù sinh sau đẻ muộn tới tận 2 năm.

Đứng trước tình hình bị soán ngôi bởi kẻ lạ mặt vừa mới xuất hiện, Facebook team phải tìm phương án nâng cấp hàng họ cho đứa con của mình trước khi quá muộn.
Và ngay sau đó 1 năm, tức năm 2018: Facebook team đã công bố kế hoạch tái kiến trúc lại (re-architecture) React Native, hứa hẹn sẽ giải quyết được các vấn đề đang vướng phải.


Nào cùng chém gió một chút về kiến trúc hiện tại của RN

Theo như format từ bao bài văn so sánh old-new, thì giờ tôi sẽ chỉ nói qua về kiến trúc hiện tại đang được facebook team sử dụng vì tôi biết thừa các bạn cũng không quá quan tâm đến thứ sắp không được sử dụng nữa đâu.

Thỉnh thoảng tôi thấy có bạn hỏi:

"Sao mà React Native có thể compile ra cả 2 OS khác nhau được vậy?"

Và đây là câu trả lời thường thấy:

"Nhờ React Native Bridge đó bạn!"

Hay nói cách khác thì là:

via GIPHY

Trong mỗi React Native application thì có 3 luồng quan trọng, đó là:

UI Thread- Hay còn được biết đến với cái tên Main Thread. Hiểu đơn giản thì nó được sử dụng để render ra UI cho Android và IOS.

JS Thread- Nơi xử lý logic nghiệp vụ của ứng dụng.
Ví dụ như đây là nơi JS code được thực thi nà, các lệnh gọi API nà, hoặc mấy cái sự kiện touch các kiểu vân vân và mây mây...

Để duy trì tốt performance thì thằng JS Thread này sẽ gửi các batched updates đến UI Thread trước khi frame tiếp theo được render.

Shadow queue- Cũng được gọi là Shadow Thread Nơi layout được tính toán và gửi về lại cho UI Thread sử dụng.

Vậy thì làm thế nào để các thread tương tác được với nhau?

Well, để 2 thằng UI Thread và JS Thread có thể giao lưu kết hợp với nhau? Bạn cần phải xây một "bridge" cho cả 2 để họ có thể "giao lưu kết hợp"...

Ở đây vì giới hạn bài viết cho phép và một phần vì lười, tôi sẽ không đi sâu vào chuyện riêng tư của người ta, nên bạn hãy tạm hiểu như trên là được.
Vậy thì tóm lại React Native gồm 3 phần chính:

  1. Native side
  2. JS side
  3. Bridge

28448598-ba471758-6df6-11e7-969b-437fb21ade3e-1

Giờ thì sang phần chính của bài này nào!

Kiến trúc mới hay còn là ước mơ hi vọng của những RN developer

Như các bạn đã thấy trong hình trên, kiến trúc mới của React Native đã tiến hành loại bỏ Bridge và thay bằng một thành phần mới tên là JSI ( Javascript Interface ). Cũng như bên phía Native side tiến hành loại bỏ Native Modules và Shadow Node ( nó chính là shadow thread ) và thay thế bằng 2 thành phần mới được gọi là Fabric và Turbo Modules.

Vậy thì vấn đề của kiến trúc hiện tại là gì?

Ta đều biết rằng, JavaScript làm việc không đồng bộ nên các tương tác của chúng ta với React component's UI cũng được xử lý không đồng bộ. JS thread listens các events, như scroll, touch các kiểu... sau đó thực hiện kết xuất DOM phù hợp và toàn bộ quá trình này đều diễn ra không đồng bộ từ Main thread cho tới UI. Chúng được gửi tiếp đến Shadow thread cho DOM và các refinements khác sẽ được gửi tiếp đến main thread queue. Yoga ( một framework) sẽ được sử dụng để chuyển đổi (transform) các response của shadow thread. Và trong khi main thread queue đang được thực hiện thì những thay đổi sẽ được reflected trên UI

Vậy thì ngay cả đối với một tương tác bất kì lớn nhỏ nào, nó sẽ phải đi qua tất cả các luồng trên, thực tế thì điều này không tệ nhưng performance hoặc đặc biệt là một vài frames sẽ bị drop vì responses không được đồng bộ với main thread trừ khi đây là những thao tác thực sự được đồng bộ hóa.

Trông có vẻ như chỉ là một vấn đề nhỏ, nhưng vấn đề nhỏ sẽ dẫn tới vấn đề lớn, và những vấn đề lớn sẽ mang cho ta những vấn đề siêu to khổng lồ. Ví dụ như khi type vào Text Input bị giật giật này, mấy cái animations bị tụt frame này, hoặc list dài quá thì render bị dính delay này, rồi lag rồi giật vân vân và mây mây...

JavaScript Interface (JSI)

Như ở kiến trúc hiện tại của React Native, giao tiếp giữa JS và Java/Obj-C method diễn ra trong bridge

  • Bridge về căn bản thì là một queue gửi những queue message (encoded thành JSON) giữa JS và Java/Obj-C. Với mỗi lần như vậy, chúng ta lại phải dequeue messages từ đầu của mỗi queue và process bọn nó. Cách gửi messaging này căn bản là không đồng bộ.
  • Bridge cũng exposes một interface cho Java/Obj-C để lên lịch thực thi Javascript, thông thường thì được sử dụng cho callbacks từ Native Modules
  • Bridge gắn liền với vòng đời của React Native App. Bắt đầu hoặc dừng app cũng đồng nghĩa với việc bridge được khởi tạo hoặc bị phá huỷ.


Thay thế cho việc phải sử dụng bridge để gửi những queueing message, kiến trúc mới cho phép chúng ta gọi trực tiếp đến các phương thức Java/Obj-C.
Trong React Native, ta có thể đơn giản là sử dùng JSI để gọi các phương thức trong UI View và Native Modules đã được implemented trong Java/Obj-C.
Hầu như code của JSI nằm trong jsi folder và được viết bằng C++, vậy nên nói JSI là một module C++ cũng không sai.


Note: JSI đã có từ phiên bản 0.59 nhưng vẫn đang phát triển và chưa được đưa vào sử dụng chính thức. Hiện đã có 1 số lib ứng dụng JSI vào sản phẩm của mình, tiêu biểu là RN reanimated 2
https://blog.swmansion.com/introducing-reanimated-2-752b913af8b3

Fabric

Tên mới của UIManager mà sẽ phải chịu trách nhiệm cho bên native side. Thay đổi lớn nhất là giờ thay thì phải giao tiếp với bên JS side qua bridge, nó sẽ expose native function bằng cách sử dụng JSI để JS side ngược lại có thể trực tiếp giao tiếp với những function đó. Ngon hơn và perfomance hiệu quả hơn nhiều so với cách cũ.

Turbo Modules.

Không có gì đáng sợ ở đây cả :)). Chỉ là tên mới cho mấy native module (Text,View,...). Giờ đây chúng được lazy-loaded ( Nghĩa là khi nào cần mới dùng tới) thay vì load tất cả trong khi khởi động. Ngoài ra, chúng cũng được exposed bằng JSI, nhờ thế JS sẽ giữ một tham chiếu đến chúng, nghĩa là không cần phải truyền sang JSON messages theo từng đợt như ở bridge của kiến trúc cũ nữa. Kết quả là thời gian khởi động sẽ nhanh hơn đáng kể.

CodeGen

Giả sử để biến bên JS side thành một Single Source Of Truth. Có thể giúp bạn tạo các static types và bên native side (Fabric and Turbo Modules) sẽ biết được và bỏ qua việc validating data mỗi lần. Việc này giúp performance tốt hơn và ít chỗ dành cho những sai lầm khi truyền dữ liệu.

Đến đây chắc là bạn cũng nắm được những gì thay đổi trong kiến trúc mới của React Native mới rồi, có thể trong tương lai xa xôi nào đó mình sẽ có những bài phân tích chi tiết cho những thành phần mới.
Còn giờ thì mình phải đi uống bia rồi, hẹn gặp các bạn ở quán bia Hải Xồm các bài viết khác, thân ái và tạm biệt!!