Quản lý trạng thái trong ứng dụng ReactJs với Redux

Trong ứng dụng react việc lưu trạng thái trong nội tại component (local state) sẽ phát sinh nhiều vấn đề đặc biệt khi props được truyền từ cha xuống con qua quá nhiều tầng hay việc mở rộng component làm cho phình to state của component dẫn tới việc khó debug, testing…để giải quyết các vấn đề này các pattern đã được ra đời, có thể kể đến một vài pattern khá nổi bật hiện nay được sử dụng nhiều flux (facebook phát triển), MobX đặc biệt là redux. Các pattern này có ưu và nhược điểm riêng trong bài viết này mình sẽ giới thiệu và hướng dẫn một cách chi tiết về redux và áp dụng trong dự án reactjs thực tế.


Bài viết sẽ bao gồm nội dung:

    1. Redux là gì? tại sao lại chọn  redux và khi nào nên sử dụng Redux trong ứng dụng react?

    2. Các thành phần trong Redux.

    3. Giao tiếp giữa các thành phần trong redux

    4. Ví dụ qua code

    5. Tài liệu tham khảo

I. Khi nào nên sử dụng Redux trong ứng dụng react?

  Redux về cơ bản nó là một design pattern được sử dụng khá phổ biến trong các ứng dụng react giúp cho việc quản lý trạng thái trở nên dễ dàng hơn, ứng dụng dễ bảo trì, dễ kiểm thử hơn. Các mẫu design pattern như mvc, mvp, mvvm, singleton, factory, composite,…ra đời đều có mục đích riêng và giải quyết các vấn đề thường gặp trong các ứng dụng enterprise. Redux cũng không ngoại lệ, đặc biệt trong các ứng dụng reactjs. Trong react flow data cơ bản được truyền từ Component cha xuống component con, flow data một chiều giúp cho lập trình viên dễ dàng trong việc debug, coding…nhưng khi ứng dụng phình to, logic component được nở cả về state, logic nội tại lẫn việc truyền dữ liệu xuống quá nhiều Component lồng nhau mọi thứ trở nên hỗn độn và khó kiểm soát, dẫn tới dễ phát sinh bugs không đáng có, làm tăng thời gian phát triển sản phẩm, với việc ra đời của redux đã giải quyết được nhiều vấn đề. 

Tại sao lại chọn redux:  redux là pattern ra đời sau flux pattern mà facebook đưa ra để giúp quản lý trạng thái trong ứng dụng react, nhưng bộc lộ nhiều nhược điểm gặp phải trong quá trình phát triển. Redux ra đời giải quyết gần như hầu hết các vấn đề mà flux gặp phải, giúp cho ứng dụng react phát triển và tổ chức tốt hơn. Redux hiên nay có một cồng đồng khá lớn, nhiều thư viện ra đời hỗ trợ pattern này giúp cho việc phát triển ứng dụng dễ dàng, đơn giản, thư viện được cộng đồng đánh giá cao, các thư viện đi kèm hỗ trợ phát triển redux trong ứng dụng react thường xuyên được bảo trì bởi các lập trình viên hàng đầu.

Khi nào thì nên sử dụng redux trong ứng dụng reactjs: 

Các pattern ra đời có điểm chung là tách bạch các thành phần trong ứng dụng ra thành các chức năng hoặc cụm chức năng đặc thù, việc tách bạch phân chia làm cho ứng dụng trở nên trong sáng, dễ dàng bảo trì, kiểm thử, …đặc biệt khi ứng dụng scale về size, code business phức tạp…nhưng đi kèm với sự tách bạch cũng có cái giá đó là mất nhiều công cài đặt hơn, người phát triển phải bỏ nhiều công sức ra tìm hiểu để nắm bắt được mọi thứ liên quan, với ứng dụng nhỏ cần thực hiện trong một thời gian ngắn…thì việc áp dụng pattern vào có vẻ không cần thiết. Mình tổng kết khi nào nên áp dụng:

1) Khi ứng dụng cần độ mở rộng (scale) tốt

2) Yêu cầu cao về độ kiểm thử và bảo trì

3) Ứng dụng có size lớn (nhiều business,…)

4) Cần sự tách bạch trong tổ chức project

II.Các thành phần trong ứng dụng redux:

1) Store (global state): 

Store chính là trung tâm, trái tim trong pattern redux có nhiệm vụ lưu trữ dữ liệu cho cả ứng dụng (data lưu trong store thường liên quan tới dữ liệu được lấy về từ api), store đồng thời cũng đưa ra hàm dispatch có nhiệm vụ đẩy action tới store để cập nhật dữ liệu. 

Một vài tips đối với store:

– dữ liệu lưu trong store nên là dữ liệu thường xuyên thay đổi hoặc được lấy về từ api. 

– dữ liệu lưu trong store nên được chuẩn hóa (normalize) không nên lưu dạng object lồng nhau quá nhiều dẫn tới việc khó cập nhật stote, dữ liệu khó thao tác.

2) UI (user interface) giao diện người dùng:

gồm có hai loại component: 

+ Container component:

component có nhiệm vụ map state và hàm dispatch từ store tới UI component thông qua hàm connect (higher order function).

+ UI component

Component chứa các thẻ html, mã jsx…mà người dùng có thể thao tác được.

3) Action

plain object với một property được được yêu cầu là type để thành phần tiếp nhận action nhận dạng được kiểu cần xử lý. action: {type: SUBMIT_TYPE}

4) Reducer

Thành phần tiếp nhận action được dispatch từ store tới, dựa vào type của action để cập nhật store.

5) Middleware

Thành phần nằm giữa UI component và store để thêm logic xử lý cần thiết ví dụ như là log mỗi khi có một action trong UI xảy ra hay xử lý call api lên server.

III. Giao tiếp giữa các thành phần trong redux


dòng dữ dữ liệu trong redux

Đây là mô hình tương tác cơ bản của các thành phần trong mô hình redux. Mình sẽ diễn tả băng lời và mô tả qua ví dụ code để dễ hình dung. Ví dụ flow login trong ứng dụng react dùng pattern redux, trong store mình sẽ cần lưu trữ các thông tin sau: isAuth (bool): kiểm tra xem người dùng đã login chưa, trạng thái này sẽ được map tới UI component, để nhận biết việc login chưa, nếu đã login thì redirect tới trang home, ngược lại show trang login để người dùng đăng nhập, một hàm onSubmit sẽ được dùng để map dispatch của store tới props của UI component: onSubmit = (username, pass) => dispatch(actionLogin(username, pass)). Sau khi định nghĩa props dùng để map và truyền vào UI component mình dùng hàm connect của redux để truyền props này xuống UI componnet. (https://github.com/reduxjs/react-redux/blob/master/docs/api.md) có dạng: connect(mapStateToProps, mapDispatchToProps)(UIComponent), lúc này UI component đã nhận được 2 props là isAuth và onSubmit. Khi người dùng gõ vào ô user và pass đồng thời nhấn submit , props onSubmit vừa map được gọi với username và pass tương ứng được nhập, khi onSubmit được gọi nó sẽ tương đương với hàm dispatch(actionLogin(userName, pass))() : đẩy action login với user và pass tới store. (actionLogin: một hàm trả về plain object action hay còn gọi là action creator) ), store nhận action không sử lý trực tiếp nó sẽ đẩy action này tới cho reducer xử lý , reducer dựa vào type cập nhập data tương ứng cho store, store thay đổi => ui tự động cập nhật theo, nếu có hoạt động call api thì action sẽ chạy qua middlware, thành phần này sẽ xử lý api lấy dữ liệu từ server và dispatch một action khác với dữ liệu lấy được tới store. Đây chính là mô tả flow data cơ bản trong ứng dụng react với tổ chức redux. Mình tổng hợp lại flow:

1) normalize  , định nghĩa data store cần sử dụng

2) map state trong store và hàm dispatch mà store đưa ra tới UI component thông qua ham connect

3) gọi props được map để dispatch action

4) viết xử lý call api trong middlware (nếu có xử lý bất đồng)

5) dựa vào type của action để cập nhật lại store với dữ liệu mới cho store từ reducer

IV) Ví dụ qua code:

https://github.com/khoand040792/template-react-redux

trên repos này mình đã setup một project dùng redux basic để mọi người có thể hiểu được giao tiếp giữa các thành phần, trong ứng dụng thực tế có thể sẽ được thay đổi cho phù hợp với yêu cầu, tích hợp thêm các thư viện hỗ trợ cho việc thao tác nhưng về cơ bản trong redux sẽ gồm những thành phần như mình đã đề cập và tương tác dữ liệu, dòng data sẽ không thay đổi. 

V.Tài liệu tham khảo

thư viện và tài liệu chính thức

thư viện hỗ trợ cho việc xử lý side effect: redux-saga

ebook redux complete (đã được review)

Một vài ví dụ hay về redux (code + giải thích)

Bài viết này chủ yếu mình muốn giới thiệu về redux, tập trung về flow data trong pattern này các thành phần tương tác với nhau, mình không tập trung quá nhiều vào cách cấu hình setup base project với Provider, root reducer, cách sử dụng api hiện có rất nhiều tài liệu trên trang chủ và cách sử dụng các api này do cộng đồng hỗ trợ. Hi vọng bài viết sẽ giúp các bạn mới học phần nào bớt mông lung hơn, giảm thời gian tìm hiểu đống thông tin hỗn độn về pattern này. Một số khái niệm đi cùng với pattern nên tìm hiểu:

+ immutable (bất biến)

+ pure function

+ higher order function, higher order component (ES6), decorator(ES7)


Tương tác phức tạp trong reactjs với react Dnd