3

React Hooks under the hood

đăng cách đây 1 năm
React Hooks under the hoodReact Hooks under the hood

Đào sâu: React Hooks hoạt động như thế nào bên dưới?

Kể từ phiên bản React 16.8 đã tung ra Hooks, một cách tiếp cận mới để quản lí state và side effect hiệu quả hơn. Tư tưởng của React Hooks đã nhanh chóng được cộng đồng JavaScript đón nhận một cách tích cực, chẳng hạn như VueSvelte hay cả core JS. Tuy nhiên, cách thiết kế Hooks đòi hỏi sự hiểu biết sâu về Closure trong JavaScript.

Bài viết này sẽ mô tả closure đơn giản bằng cách clone một phiên bản “React Hooks” đơn giản.

Closure: giúp thao tác với state trong JavaScript

State trong các ngôn ngữ lập trình đơn giản có nghĩa là việc lưu giữ các giá trị.

Ví dụ:

Trong ví dụ trên, state được lưu giữ trong biến a. Để kiểm soát sự thay đổi của một đối tượng, chúng ta phải ghi lại state của nó. Thứ nắm giữ state trong ví dụ trên là biến a, nó giúp giữ state trong bộ nhớ.

Thông thường khi lập trình, bạn sẽ muốn theo dõi mọi thứ, ghi nhớ trạng thái các đối tượng và truy cập nó sau. Trong các ngôn ngữ lập trình OOP phổ biến (JS với ES6+), điều này được thực hiện bởi khái niệm class và instance, ví dụ dễ hiểu như sau:

Tuy nhiên, với JavaScript (không hỗ trợ Class từ đầu!) và trong các ngôn ngữ chỉ hỗ trợ functional programming (như Elixir, Lisp), chúng ta có thể sử dụng Closure như một cách quản lí state hiểu quả và tối ưu nhất.

Ví dụ:

Trong ví dụ trên đã hoàn thành việc lưu một state vào biến global n, tuy nhiên cách này không an toàn lắm khi biến n được expose ra và bất kì một hàm nào khác cũng có thể thay đổi giá trị của biến này. Chúng ta có thể làm tốt hơn với closure đơn giản giúp đóng gói state trong một hàm:

Pro ghê. Trong ngữ cảnh của functional programming, nơi function la first-class-citizen, chúng ta có thể thao tác được với state và side effect mà không cần tới những thứ như class, method hay instance.

Closure là gì?

Một trong những lợi thế khi sử dụng Hooks trong React là giảm thiểu sự phức tạp, loại bỏ các side effect gây nhức đầu và khó tái sử dụng do việc sử dụng class và higher order component gây ra. Tuy nhiên, khi dùng Hooks thì lại có một vấn đề khác cần lưu tâm. Thay vì quan tâm về bound context (class, this…) thì chúng ta sẽ bị confuse bởi Closure:

ClosureClosure

Closure là một trong những concept cơ bản của Javascript và các ngôn ngữ hỗ trợ functional programming khác (như Elixir, Lisp hay cả Ruby), tuy vậy đây vẫn luôn là cơn ác mộng với nhiều developer khi mới bắt đầu. Kyle Simpson trong quyển You Don’t Know JS định nghĩa Closure như sau:

“Closure is when a function is able to remember and access its lexical scope even when that function is executing outside its lexical scope.”

hoặc

“Closure là những function tham chiếu đến các biến tự do tách biệt. Nói cách khác, function được định nghĩa trong closure sẽ ghi nhớ môi trường (lexical environment) trong nó được tạo ra.”

Để dễ hiểu hơn, chúng ta sẽ clone hàm useState của React Hooks bằng closure như sau:

Xong, chúng ta đã hoàn thành viện clone lại hàm useState của React Hooks. Hàm này trả về 2 hàm con, state và setStatestate trả về biến cục bộ _val được khai báo ở trên và setState gán biến cục bộ này bằng giá trị được truyền vào (newVal). Với foo và setFoo chúng ta đã có thể thao tác với một biến cục bộ (một state).

Dùng useState trong Functional Components

Hãy thử mô phỏng việc sử dụng hàm useState mới tạo ở trên trong một Function Component, ví dụ như một Counter Component như sau:

Ở hàm này chúng ta chưa xuất ra được DOM mà mới chỉ in ra bằng console.log, tuy nhiên hàm mô phỏng này đã tạm đủ để hiểu được việc sử dụng useState trong Component của React.

Tuy vậy, hàm này vẫn chưa tương thích được 100% với React.useState API thực sự, vì count vẫn con đang là một hàm thay vì một biến (variable).

Cải thiện useState để tương thích với React.useState API

Để biến count thành một biến thay vì một hàm cũng không phải chuyện dễ. Nếu chỉ đơn giản expose ra biến _val thay vì wrap nó trong một hàm, sẽ có bug ngay:

Đây là một vấn đề liên quan tới Stale Closure. Khi chúng ta destructure biến foo từ hàm useState, nó sẽ luôn là giá trị khởi đầu (initial value) của hàm useState mà không bao giờ trả về giá trị hiện tại (current value).

Cải thiện Closure với Module Pattern

Chúng ta có thể giải quyết vấn đề trên bằng cách… di chuyển closure vào một closure khác. 

Như cách viết ở trên, chúng ta đã sử dụng Module pattern để tạo ra một hàm useState tương thích với React API. Cách này cho phép MyReact “render” function component, cho phép gán lại giá trị của biến cục bộ _val mỗi lần với closure chuẩn xác:

Bạn có thể đọc thêm về Module Pattern và Closure trong You Don’t Know JS.

Tiếp tục clone useEffect

Sau useState, một React Hook cơ bản nhất, thì useEffect cũng là một API khá quan trọng của React Hooks (nó sẽ thay thế cho các lifecycle của React Class Component như ComponentDidMount hay ComponentDidUpdate…). Không như useState, useEffect được thực thi một cách bất đồng bộ, có nghĩa rằng nó sẽ có nhiều khả năng bị lỗi hơn (đối mặt với các vấn đề của closure).

Chúng ta có thể mở rộng MyReact ở trên để tiếp tục implement bản clone của useEffect:

Để theo dõi các dependencies (vì useEffect sẽ chạy lại mỗi khi một trong các dependencies thay đổi), chúng ta sẽ sử dụng một biến khác để theo dõi: _deps.

Dùng Array với Hooks

Các hàm clone useState và useEffect ở trên gần như đã hoạt động tốt về mặt chức năng, tuy nhiên chúng ta sẽ gặp vấn đề khi sử dụng cả 2 hàm này cùng lúc (chỉ có thể tồn tại 1 trong 2, hoặc sẽ có bug). Để giải quyết vấn đề này, chúng ta sẽ dùng hooks array, như Rudi Yardley đã viết: React Hooks không có gì là ma thuật cả, chỉ với array Chúng ta có thể thu gọn _val và _deps vào hooks array vì chúng sẽ không bao giờ trùng nhau:

Giải thích gọn: sẽ có một array của các hooks và một chỉ mục (currentHook) sẽ tăng lên mỗi khi có một hook được gọi, và được reset mỗi khi component được render lại.

Nguồn gốc các quy tắc của React Hooks

Qua cách implement bản clone của Hooks ở trên các bạn có thể hiểu sơ được về một trong những quy tắc sử dụng React Hooks: chỉ được gọi Hooks ở Top Level. Chúng ta đã mô hình hóa một cách rõ ràng sự phụ thuộc của React vào thứ tự các lần gọi với biến currentHook như ở trên. Bạn có thể đọc thêm về mô tả về quy tắc này tại trang chủ docs của React.

Bài viết này sẽ giúp tôi tăng lương như thế nào? (copyright by quan-cam blog)

Bài viết này không giúp bạn tăng lương :(, nhưng hi vọng nó đã cung cấp cho bạn một cái nhìn rõ ràng hơn về cách mà React Hooks hoạt động cũng như Closure trong Javascript.

Ghé Tú Blog tại đây nha :wink:

References

#hooks
3
0
...