Quản lí trạng thái trong Angular

Một trong những đặc điểm thường được nhắc đến của Angular là liên kết dữ liệu 2 chiều (two ways binding): Dữ liệu thay đổi ở model sẽ được cập nhật trực tiếp lên view và mọi thay đổi trên view sẽ tự động cập nhật sang model.

Kết nối dữ liệu hai chiều là một đặc điểm tuyệt vời của Angular khi làm đơn giản hóa việc kết nối dữ liệu giữa model và view. Tuy nhiên, khi ứng dụng cần mở rộng, ngày càng nhiều hàm xử lí cùng tương tác đến một biến toàn cục, khiến mất kiểm soát sự thay đổi giá trị của biến. Do đó, nếu ứng dụng Angular của bạn có định hướng phát triển lâu dài thì nên cân nhắc đến việc áp dụng quản lí trạng thái (State Management).

Trong bài viết này mình sẽ giải thích về quản lí trạng thái và kiến trúc của Redux. Redux là cái tên quá quen thuộc khi làm ReactJS nhưng Angular thì ít nghe nhắc đến nhỉ. ?

Trạng thái (State):

Giải thích đơn giản thì state bao gồm trạng thái của giao diện và trạng thái của biến. Bất kì thay đổi trong ứng dụng của bạn đều thay đổi trang thái. Ví dụ: khi ta click vào nút để tăng giá trị của số đếm lên, ta đã thay đổi trạng thái giá trị của biến đếm, đồng thời làm thay đổi trạng thái giao diện:

Trong Angular và các framework javascript khác, mỗi thành phần (component) đều có trạng thái độc lập với nhau, trạng thái của thành phần này sẽ không ảnh hưởng trạng thái của thành phần khác trừ khi chúng ta không liên kết luồng dữ liệu giữa các thành phần lại với nhau. Trong Angular, ta dùng @Input@Output decorators để liên kết luồng dữ liệu giữa thành phần cha và thành phần con.

Ứng dụng Angular đơn giản

Việc truyền dữ liệu giữa các component thật dễ dàng nếu ứng dụng có kiến trúc đơn giản trên. Tuy niên, mọi chuyện sẽ trở nên phức tạp hơn nếu ứng dụng có kiến trúc như ảnh dưới đây.

Nếu như bạn muốn chuyển dữ liệu từ component 2 sang component 5, bạn phải thực hiện 3 lần xử lí sự kiện thay đổi đổi trang thái.

Dòng di chuyển của dữ liệu từ component 2 sang component 5

State Management

Sau một hồi phân tích thì đưa đến kết luận là nên áp dụng quản lí trang thái vào. Một trong những kiến trúc được áp dụng nhiều nhất hiện nay là Redux.

Có 3 nguyên tắc cơ bản trong kiến trúc của Redux.

Thứ nhất: 1 store duy nhất.

Dữ liệu được lưu trữ ở một nơi duy nhất, gọi là store. Store sẽ chỉ có chức năng duy nhất là cung cấp dữ liệu cho component. Điều này mang đến sự thay đổi của xử lí dòng dữ liệu, cách truyền thống là từ componen-to-component được đổi thành store-to-component.

Store giống như một cơ sở dữ liệu tập trung

Thứ 2: Trạng thái chỉ đọc.

Các trạng thái trong Redux nên chỉ có thể đọc (read-only). Để thay đổi trạng thái, một hành động (action) phải được phát ra.

Thứ 3: Pure function reducers

Đầu tiên, xin giải thích tí thế nào là pure function. Pure function là hàm mà không bị phụ thuộc và cũng không bị thay đổi trạng thái bởi các biến bên ngoài phạm vi hoạt động của hàm.

Ví dụ về một không phải là pure function:

var number;

function ascrease(){
  return number + 1;
}

Trong đoạn code trên, kết quả trả về của hàm ascrease sẽ bị phụ thuộc vào biến toàn cục là number.

Ta tiến hành sửa hàm trên thành pure function như sau:

function ascrease(number){
  return number + 1;
}

 

Tiếp tục giải thích về reducer. Trong Redux thì reducer có nhiệm vụ là lấy trạng thái hoặc trả về trạng thái tiếp theo. Trong Redux thì các reducer nên được viết dưới dạng pure function.

Một điểm quan trọng khác của Redux là tất cả dữ liệu trong ứng dụng chỉ đi theo một chiều. Quá trình đi của dòng dữ liệu như sau:

Khi muốn thay đổi dữ liệu, component gửi công văn (dispatch) đến store. Reducer của của store thực hiện action và xử lí để tạo nên trạng thái mới. Component lấy trạng thái mới thông qua lệnh select.

Các thành phần của Redux:

Store:

Store là kiến trúc cở bản chứa action, reducer và trạng thái. Store sẽ nhận đầu vào là action, sau đó chuyển đổi action thành reducer. Reducer sẽ thực hiện xử lí để tạo ra trạng thái mới, sau đó phát đi trạng thái mới. Store sẽ giữa trạng thái cho đến khi trạng thái này bị thay đổi bởi một action mới.

Action:

Một action được chia làm 2 phần: phần loại (type) và dữ liệu mang theo (payload). Type là phần bắt buộc phải có để reducer có thể phân biệt giữa các action với nhau, do đó, mỗi action phải có type phân biệt và duy nhất.

Có thể xem action như một chiếc xe container. Một container thì phải có đầu kéo có biển số xe phân biệt và duy nhất (type), ngoài ra xe xe container có thể mang theo hàng hoặc không (payload).

Ví dụ, ta có thể định nghĩa 1 action bằng typescript như sau:

interface Action {
  type: string,
  payload?: Payload
}
SearchAction = {type: 'SEARCH', payload: 'WTF Redux?'};
          
IncrementAction = {type: 'INCREMENT'} // reducer will add +1 current state

IncrementByAction = {type: 'INCREMENTBY', payload: 2} //reducer will add 2 to the current state

Reducer

Reducer là phần cốt lõi của store có nhiệm vụ là thực hiện thay đổi trạng thái. Reducer sẽ nhận action và state làm tham số đầu vào và xử lí để tạo ra state mới.

Điều cần lưu ý, một trong những đặc điểm của Redux đã đề cập phía trên: trạng thái là bất biến trong Redux, reducer không "đột biến" trạng thái, mà chỉ tạo một trạng thái mới. Khó hiểu vãi nhỉ. ?

Quá trình xử lí của reducer

Kết:

Trên đây là giải thích sơ lượng về state management. Nhìn chung trong ứng dụng angular thì nếu muốn phát triển ứng dụng nhanh thì cứ truyền dữ liệu component-to-component cho đơn giản, dễ hiểu. Bạn thấy rằng các khái niệm của Redux đề cập ở trên không hề dễ hiểu tí nào cho newbie. ?

Còn nếu muốn đi lâu dài, dễ mở rộng thì nên áp dụng quản lí trạng thái vào. Một phần là giờ nhà nhà dùng Redux, mình không không theo thì lỗi thời quá. ? Thật ra thì bây giờ trong cộng đồng React nổi lên phong trào là Pure React, đòi loại bỏ Redux, thay vào đó là áp dụng kiến trúc pure component để tinh gọn, để hiểu hơn. Tuy nhiên, thực hư thế nào về pure component sẽ đề cập trong bài viết khác vì bài này đã lỡ quảng cáo Redux rồi.?

Trong phần tiếp theo, chúng ta sẽ tiến hành cài đặt Redux cho ứng dụng Angular nhé.

Categories: