Hướng dẫn sử dụng Redux hiệu quả trong ứng dụng React
Câu hỏi: Khi nào thì nên sử dụng Application State (Redux Store), khi nào thì nên sử dụng Local State khi áp dụng Redux cho React (React Js & React Native)?
Câu trả lời ngắn gọn: tùy thuộc vào bạn, làm sao viết code thoải mái để Application State tối giản nhất có thể mà không làm mất tính tin tưởng vào ứng dụng.
(Bài viết này sử dụng cho cả React Js và React Native)
Một số khái niệm
Application State: hay còn gọi là Redux Store chứa trạng thái của ứng dụng bao gồm dữ liệu từ máy chủ, kết quả tương tác/hành động của người dùng, dữ liệu từ thiết bị (GPS, Clock, Accelerometer…)
Local State: là trạng thái nội tại của component, sự thay đổi bất kỳ trong Local State không làm ảnh hưởng đến Application State
Presentation Component: là component chỉ có nhiệm vụ hiển thị, nhận tương tác từ người dùng.
Container Component: là component bao bọc Stateless/Presentation component và truyền dữ liệu cho các component này bằng props.
Stateless Component: đây là một phiên bản của Presentation Component nhưng không chứa state, thông thường được viết bằng hàm đơn thuần, mọi thứ đều được truyền thông qua function’s arguments (props).
Khi nào nên sử dụng Redux
Trước tiên, xin trích dẫn những câu trả lời từ những người tạo ra React và Redux:
Pete Hunt (Cựu thành viên tạo ra React):
You’ll know when you need Flux. If you aren’t sure if you need it, you don’t need it.
Dan Abramov (Tác giả Redux):
I would like to amend this: don’t use Redux until you have problems with vanilla React.
Như vậy, chỉ sử dụng Redux/Flux khi và chỉ khi bạn gặp vấn đề với React. Lý thuyết là vậy, nhưng để biết được tình huống nào gọi là “vấn đề” quả thật không hề dễ dàng, và khi đã gặp vấn đề thì lượng code đã viết cũng đã trở nên kha khá, và việc tích hợp Redux sẽ trở nên khó khăn hơn – tất nhiên không phải là quá khó, nhưng thay vì business logic bạn đặt ở 1 chỗ (có thể chia nhiệm vụ cho từng thành viên trong nhóm khác nhau, có thể test dễ dàng hơn) thì business logic được đặt ngay trong chính React’s component => việc tái cấu trúc luồng hoạt động của dự án ước chừng sẽ khó khăn là vì vậy.
Như hình mô tả dưới đây, mỗi node là 1 component, việc tách ra trạng thái tổng, giúp chúng ta dễ bảo trì phần Model (business logic) và phần View (component) trong mô hình MVC, cũng như dễ dàng sử dụng SSR (Server Side Rendering):
Theo điều kiện nguồn lực, dần dà việc tái cấu trúc dự án để sử dụng Redux dần trở nên bất khả thi, nhất là đối với những dự án không biết tách “Container Component” và “Presentation Component” ra riêng biệt:
Presentational Components | Container Components | |
---|---|---|
Purpose | How things look
(markup, styles) |
How things work
(data fetching, state updates) |
Aware of Redux | No | Yes |
To read data | Read data from props | Subscribe to Redux state |
To change data | Invoke callbacks
from props |
Dispatch Redux actions |
Are written | By hand | Usually generated
by React Redux |
Do vậy, yếu tố quyết định là phải phân biệt được “Container Component” và “Presentation Component”, khi đó Container Component sẽ làm việc với Application State, còn Presentation Componnent sẽ có Local State.
Một điều dễ nhận ra là khi áp dụng Redux, mặc dù nó tốt thiệt – giúp cho ứng dụng dễ hiểu, dễ bảo trì, nhưng lượng code phải viết sẽ nhiều lên kha khá. Cứ mỗi chức năng, ta cần phải viết Actions/Events -> Reducers -> Container Component -> Presentation Component, tương ứng bạn cũng sẽ viết Test cho chừng đó bước, vì vậy việc sử dụng Application State một cách hợp lý là điều cực kỳ quan trọng, giúp cho ứng dụng gọn nhẹ về mặt cấu trúc, code, và tài nguyên (bộ nhớ để lưu trữ Store)
Muốn tìm cách cải thiện ứng dụng sao cho dễ hiểu, dễ bảo trì thì đọc tiếp :). Mình cũng từng như bạn, cũng như rất nhiều lập trình viên khác sử dụng Redux bị rối khi thắc mắc không biết là khi nào nên dùng Application State (trạng thái của ứng dụng) khi nào nên dùng Local State (trạng thái của component) để làm sao đạt được mục đích là:
- Application State tối giản nhất có thể
- Sử dụng Local State để viết code ngắn gọn nhất có thể
Nếu bạn đọc xong mà vẫn không thích Redux, thì khi có vấn đề về việc trao đổi dữ liệu giữa component cha-con thì đọc ở bài: “Sử dụng context để giao tiếp giữa các component cha-con trong React”
Sử dụng Redux với React sao cho hiệu quả
Nếu bạn có sử dụng React Native thì cũng có biết là từ phiên bản 0.25 trở đi, thì cách viết import, creatClass có khác đôi chút:
0.24 trở về trước
import React, { Component, View } from 'react-native';
0.25+
import React, { Component } from 'react'; import { View } from 'react-native';
Qua đó để thấy rằng, React Native quay trở về đúng bản chất của nó khi hệ sinh thái React ra đời, đó là React Native chỉ đóng vai trò phần View, còn React sẽ đóng vai trò xương sống khởi tạo hệ sinh thái React. Mình đưa ra ý này là để nhấn mạnh tầm quan trọng của sự tách biệt giữa các thành phần, càng nhỏ thì càng dễ bảo trì, do vậy nếu vẫn chưa thông ở phần “Khi nào nên sử dụng Redux” thì bạn hãy nghiên cứu thêm rồi đọc tiếp nhé.
Làm sao để sử dụng Redux hiệu quả, thì câu trả lời đơn giản là một Câu hỏi: Có các “thành phần” bất kỳ nào của ứng dụng sử dụng “kết quả” của hành động thay đổi trạng thái hay không?
“Thành phần” ở đây có thể là UI components, hoặc lưu trữ vào local store, hoặc lưu trữ ở server. Chỉ cần mỗi khi bạn bắt đầu viết 1 chức năng, component hãy đặt câu hỏi trên cho những tương tác vào ứng dụng (có thể sự kiện từ nhiều phía khác nhau), thông thường Local State sẽ được sử dụng để quản lý sự tương tác của người dùng, ví dụ những sự kiện sau thì không cần thiết phải sử dụng Application State:
- Toggle một menu, hoặc accordion
- Input text, radio, select..: bình thường sẽ sử dụng Local State
- Nếu các loại input là dạng search realtime, hoặc freeze search, filter cho mỗi ký tự nhập vào thì dùng Application State, bởi vì nó có tác động đến component khác
- Những tương tác của người dùng đối với ứng dụng chỉ là tạm thời mà có thể khi refresh ứng dụng, bạn không còn quan tâm. Vì nếu quan tâm, nghĩa là cần lưu trữ ở đâu đó, có nghĩa là nó đã tác động đến “thành phần” khác thì nên sử dụng Application State.
Fullstack Station’s Tips
Có nhiều lập trình viên khuyên nên viết Stateless Component bằng hàm thông thường, ví dụ:
import React, { PropTypes } from 'react' const Todo = ({ onClick, completed, text }) => ( <li onClick={onClick} style={{ textDecoration: completed ? 'line-through' : 'none' }} > {text} </li> )
Bạn thường cũng sẽ viết theo, nhưng do lười biếng nên thiếu đoạn code sau:
Todo.propTypes = { onClick: PropTypes.func.isRequired, completed: PropTypes.bool.isRequired, text: PropTypes.string.isRequired }
Và nếu tốt hơn nữa thì có thêm defaultProps cho những props không bắt buộc truyền vào. Việc thêm propTypes vào các component là cần thiết, để React sẽ kiểm tra cho bạn tính đúng đắn của component khi thực thi, sẽ báo cho bạn biết nếu props bị thiếu, từ đó giúp cho chất lượng sản phẩm được nâng lên, ít lỗi hơn. Rõ ràng với đoạn mã trên, nếu không ràng buộc bắt buộc thì sự kiện onClick sẽ bị lỗi nếu truyền props bị thiếu!
Lời kết
Khi bạn áp dụng Redux vào React, những vấn đề về việc khi nào nên sử dụng Application State hay Local State đã được giải đáp phần nào trong bài viết này. Tùy trường hợp vào mỗi dự án có tính chất khác nhau mà việc áp dụng cũng sẽ linh động khác nhau, chứ không nhất thiết phải theo một mô hình nào, bởi vì ngay cả người tạo ra cũng không thể dự tính được hết các tình huống sử dụng thực tế của sản phẩm được. Nếu bạn có ý kiến để hỗ trợ bài viết tốt hơn, vui lòng comment nhé!
Bài viết tới, mình sẽ bàn luận tới 1 vấn đề cũng không có cách giải quyết hoàn hảo, đó là: nên đặt business logic vào actions hay reducers trong Redux?