Chuyển đến nội dung chính

Blazor: Fluxor là gì? - Part 4

Fluxor là một thư viện giúp quản lý trạng thái (State Management) một cách chặt chẽ và dễ dự đoán. Để hiểu cách nó hoạt động, chúng ta hãy nhìn vào luồng dữ liệu sau:

1. Luồng hoạt động

graph LR A[UI / View] -- Dispatch --> B(Action) B --> C{Dispatcher} C --> D[Reducer] E[(Current State)] --> D D -- Create New --> F[(New State)] F --> A

Hiểu đơn giản:

  • State (Trạng thái): Là dữ liệu duy nhất và không thể thay đổi (immutable) đại diện cho một phần của ứng dụng.
  • Action (Hành động): Một thông điệp gửi đi để yêu cầu thay đổi trạng thái. Nó không chứa logic, chỉ chứa dữ liệu cần thiết.
  • Reducer (Bộ chuyển đổi): Một hàm thuần túy (pure function) nhận vào State cũ + Action và trả về một State mới.
  • Dispatcher (Bộ điều phối): Công cụ dùng để gửi Action vào hệ thống.

State

State trong Blazor là dữ liệu hiện tại của ứng dụng tại một thời điểm cụ thể (ví dụ: giỏ hàng, thông tin đăng nhập, biến

Lưu ý: Không ai được sửa State trực tiếp.

Tại sao State phải "Bất biến" (Immutable)?

Hãy tưởng tượng bạn có một danh sách đơn hàng. Nếu bạn sửa trực tiếp giá tiền của một đơn hàng trong danh sách, UI của Blazor sẽ phải quét qua toàn bộ danh sách để so sánh từng thuộc tính xem cái nào vừa đổi để cập nhật. Việc so sánh "từng li từng tí" này (Deep Comparison) cực kỳ tốn tài nguyên.

Với Immutability:

  • Khi cần đổi dữ liệu, bạn tạo một bản sao State mới (một địa chỉ ô nhớ mới).
  • Blazor chỉ cần so sánh địa chỉ: OldState == NewState?. Nếu khác địa chỉ, nó biết chắc chắn dữ liệu đã đổi và cần vẽ lại UI.
  • Việc so sánh hai địa chỉ ô nhớ (Reference Comparison) diễn ra gần như tức thời.

Nếu có 2 biến (variables) thì có cần 2 State không?

Điều này phụ thuộc vào việc 2 biến đó có cùng mục đích sử dụng (Use Case) hay không.

Trường hợp A

Hai biến thuộc cùng một tính năng (1 State) 

Nếu bạn có FirstName và LastName. Vì chúng luôn đi cùng nhau trong một "User Profile", bạn nên để chúng trong cùng một State.

public class UserState {
    public string FirstName { get; }
    public string LastName { get; }
    // Constructor...
}
Khi đổi FirstName, bạn tạo một UserState mới chứa FirstName mới và LastName cũ.

Trường hợp B

Hai biến độc lập (2 States/Features)

Nếu bạn có biến Counter (đếm số) và biến ThemeColor (màu giao diện). Chúng chẳng liên quan gì đến nhau.

Bạn nên tạo 2 State riêng biệt (trong Fluxor gọi là 2 Feature).

Việc tách nhỏ giúp ứng dụng của bạn không bị "render lại toàn bộ" một cách vô lý. Khi Counter thay đổi, những UI chỉ dùng ThemeColor sẽ không bị ảnh hưởng.

graph TD Store[Fluxor Store] Store --> Feature1[Counter Feature State] Store --> Feature2[User Profile Feature State] Store --> Feature3[Settings Feature State] Feature1 --> Var1[ClickCount] Feature2 --> Var2[Name] Feature2 --> Var3[Email]

Các cách quản lý State thực tế (Hybrid)

Trong các dự án thực tế, người ta thường kết hợp nhiều cách:
  • Fluxor: Quản lý dữ liệu nghiệp vụ chính (Danh sách sản phẩm, Giỏ hàng, Thông tin User).
  • State Container: Quản lý trạng thái UI tạm thời (Đóng/mở Sidebar, trạng thái Loading của một vùng cụ thể).
  • Cascading Value: Quản lý các cài đặt chung (Ngôn ngữ, Dark/Light Mode).

2. 3 thành phần chính

State (Dữ liệu)

Là dữ liệu hiện tại của app.

public class CounterState
{
    public int ClickCount { get; }

    private CounterState() {}  // Required for creating initial state

    public CounterState(int clickCount)
    {
        ClickCount = clickCount;
    }
}

Ghi nhớ: State chỉ đọc, không sửa.

---

Action (Yêu cầu)

Là tín hiệu nói rằng: "Tôi muốn làm gì đó".

public class IncrementCounterAction {}

Không chứa logic.

Khi bạn muốn một hành động không chỉ là "gửi tín hiệu" mà còn kèm theo dữ liệu (như số lượng tăng thêm, thông tin người dùng, hoặc nội dung nhập từ form), bạn chỉ cần khai báo thêm các thuộc tính (properties) vào class Action.

Ví dụ: Bạn muốn tăng số lượng nhưng kèm theo tên người thực hiện và ghi chú.

public class IncrementCounterAction
{
    public int Amount { get; }
    public string UpdatedBy { get; }
    public DateTime Timestamp { get; }

    public IncrementCounterAction(int amount, string updatedBy)
    {
        Amount = amount;
        UpdatedBy = updatedBy;
        Timestamp = DateTime.Now;
    }
}
Cập nhật Reducer để xử lý dữ liệu mới
[ReducerMethod]
public static CounterState ReduceIncrementCounterAction(CounterState state, IncrementCounterAction action) =>
  new CounterState(
      clickCount: state.ClickCount + action.Amount,
      lastUpdatedBy: action.UpdatedBy // Giả sử State có field này
  );
Dispatch từ UI
// Truyền bao nhiêu tham số tùy thích qua Constructor của Action
Dispatcher.Dispatch(new IncrementCounterAction(10, "Admin"));
---

Reducer (Xử lý)

Nơi duy nhất được phép thay đổi State.

[ReducerMethod]
public static CounterState ReduceIncrementCounterAction(
    CounterState state,
    IncrementCounterAction action)
{
    return new CounterState(state.ClickCount + 1);
}
Lưu ý quan trọng khi dùng tham số trong Action:
  • Tính Read-only: Tốt nhất nên để các thuộc tính trong Action là get; (chỉ đọc) và gán qua Constructor. Điều này đảm bảo dữ liệu không bị thay đổi lung tung trên đường vận chuyển.
  • Action không chứa logic: Action chỉ nên chứa dữ liệu thô. Đừng thực hiện tính toán bên trong Action, hãy để việc đó cho Reducer.
  • Phân tách rõ ràng: Nếu bạn thấy Action bắt đầu chứa quá nhiều tham số không liên quan, đó là dấu hiệu bạn nên chia nhỏ nó thành nhiều Action khác nhau.

Công thức: State cũ + Action → State mới


3. Cách sử dụng trong App

Bạn cần:

  • IState để đọc dữ liệu
  • IDispatcher để gửi Action
public class App
{
    private readonly IDispatcher Dispatcher;
    private readonly IState<CounterState> CounterState;

    public App(IDispatcher dispatcher, IState<CounterState> counterState)
    {
        Dispatcher = dispatcher;
        CounterState = counterState;

        CounterState.StateChanged += (_, _) =>
        {
            Console.WriteLine($"Count: {CounterState.Value.ClickCount}");
        };
    }

    public void Run()
    {
        while (true)
        {
            var input = Console.ReadLine();

            if (input == "1")
                Dispatcher.Dispatch(new IncrementCounterAction());
        }
    }
}

Khi gọi:

Dispatcher.Dispatch(new IncrementCounterAction());

Thì:

  1. Reducer chạy
  2. Tạo State mới
  3. UI tự cập nhật

4. Lưu ý quan trọng

Sai:

state.ClickCount++; // Không nên

Đúng:

return new CounterState(state.ClickCount + 1);

Không viết logic phức tạp trong Reducer (API, database...)


5. Tư duy khi dùng Fluxor

  • Không sửa dữ liệu trực tiếp
  • Mọi thay đổi đều có “dấu vết”
  • Dễ debug hơn

Không còn kiểu: “Ai sửa biến này vậy?”


6. Cấu trúc thư mục

Không nên:

State/
Action/
Reducer/

Nên: (Dạng Folder Structure truyền thống)

/Store
  /CounterUseCase
    ├── CounterState.cs            // Định nghĩa State
    ├── IncrementCounterAction.cs   // Định nghĩa Action
    ├── Reducers.cs                 // Chứa các hàm xử lý logic (Static)
    ├── Effects.cs                  // (Nâng cao) Xử lý gọi API, Side effects
/Pages
    ├── Counter.razor               // UI sử dụng State
/Shared
    ├── MainLayout.razor

Kết luận

  1. Không sửa State trực tiếp
  2. Muốn đổi → Dispatch Action
  3. Reducer tạo State mới

Nắm được 3 điều này là bạn hiểu Fluxor cơ bản rồi.

Nhận xét

Bài đăng phổ biến từ blog này

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh. Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.

ASP.NET MVC: Cơ bản về Validation

Validation (chứng thực) là một tính năng quan trọng trong ASP.NET MVC và được phát triển trong một thời gian dài. Validation vắng mặt trong phiên bản đầu tiên của asp.net mvc và thật khó để tích hợp 1 framework validation của một bên thứ 3 vì không có khả năng mở rộng. ASP.NET MVC2 đã hỗ trợ framework validation do Microsoft phát triển, tên là Data Annotations. Và trong phiên bản 3, framework validation đã hỗ trợ tốt hơn việc xác thực phía máy khách, và đây là một xu hướng của việc phát triển ứng dụng web ngày nay.

Tổng hợp một số kiến thức lập trình về Amibroker

Giới thiệu về Amibroker Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”. AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.