Advertisement
  1. Code
  2. Redux

Bắt đầu với Redux: Tại sao Redux?

Scroll to top
Read Time: 16 min

Vietnamese (Tiếng Việt) translation by Thai An (you can also view the original English article)

Khi bạn đang học React, bạn sẽ hầu như luôn nghe mọi người nói Redux tuyệt vời thế nào và bạn nên thử dùng nó. Hệ sinh thái React đang phát triển với tốc độ nhanh chóng và có rất nhiều thư viện mà bạn có thể kết nối với React, chẳng hạn như flow, redux, middlewares, mobx, v.v.

Học React rất dễ, nhưng việc quen thuộc với toàn bộ hệ sinh thái React cần có thời gian. Hướng dẫn này giới thiệu về Redux - một trong các component không thể thiếu của hệ sinh thái React.

Thuật ngữ Non-Redux cơ bản

Dưới đây là một số thuật ngữ thường được sử dụng mà có thể bạn có thể chưa quen thuộc, nhưng chúng không dành riêng cho Redux. Bạn có thể lướt qua phần này và quay lại khi/nếu có điều gì đó không rõ.

Hàm Pure

Một hàm thuần tuý (pure) chỉ là một hàm bình thường với hai ràng buộc bổ sung mà nó buộc thỏa mãn:

  1. Với một tập hợp các dữ liệu đầu vào, hàm luôn luôn trả về cùng một kết quả.
  2. Hàm này không sinh ra tác dụng phụ.

Ví dụ, đây là một hàm thuần túy trả về tổng của hai số.

1
/* Pure add function */
2
const add = (x,y) => {
3
  return x+y;
4
}
5
 
6
console.log(add(2,3)) //5

7

Các hàm thuần túy mang đến một kết quả có thể dự đoán được và xác định. Một hàm trở gọi là không thuần tuý khi nó thực hiện bất kỳ điều gì khác ngoài việc tính toán giá trị trả về.

Ví dụ, hàm add bên dưới sử dụng trạng thái toàn cục để tính ra kết quả. Ngoài ra, hàm này cũng xuất ra giá trị trong console, được xem như một hiệu ứng phụ.

1
const y = 10;
2
3
const impureAdd = (x) => {
4
  console.log(`The inputs are ${x} and ${y}`);
5
  return x+y;
6
}

Observable Side Effects

"Observable side effects" (hiệu ứng phụ có thể quan sát) là một thuật ngữ ưa thích cho các tương tác được thực hiện bởi một hàm với môi trường bên ngoài. Nếu một hàm cố gắng ghi một giá trị vào một biến tồn mà biến này tại bên ngoài hàm hoặc cố gắng gọi một phương thức bên ngoài hàm, thì bạn có thể yên tâm gọi chúng là side effect (hiệu ứng phụ).

Tuy nhiên, nếu một hàm thuần túy gọi một hàm thuần túy khác, thì hàm này có thể được xem là thuần túy. Dưới đây là một số hiệu ứng phụ thường gặp:

  • gọi API
  • ghi lại vào console hoặc in dữ liệu
  • chuyển đổi dữ liệu
  • Xử lý DOM
  • truy xuất thời gian hiện tại

Các thành phần container và presentational

Việc chia kiến ​​trúc của component thành hai phần rất hữu ích đối với các ứng dụng React. Bạn có thể phân chúng thành hai loại: container và presentational (để trình bày). Hai loại component này được nhắc đến nhiều với tên gọi smart và dumb.

Container liên quan đến cách mọi thứ hoạt động, trong khi các presentational có liên quan đến cách mọi thứ hiển thị như thế nào. Để hiểu các khái niệm tốt hơn, tôi đã viết chúng trong một hướng dẫn khác: Container vs. Presentational Components in React.

Các đối tượng Mutable vs. Immutable

Một đối tượng mutable (có thể thay đổi) được định nghĩa như sau:

Một đối tượng mutable có thể thay đổi sau khi nó được tạo ra.

Immutability (tính bất biến) là một đối nghịch chính xác - một đối tượng immutable (bất biến) có trạng thái không thể sửa đổi sau khi được tạo ra. Trong JavaScript, chuỗi (string) và số (number) là không thay đổi, nhưng đối tượng (object) và mảng (array) thì không. Ví dụ này minh họa cho sự khác biệt rõ hơn.

1
/*Strings and numbers are immutable */
2
3
let a = 10;
4
5
let b = a;
6
7
b = 3;
8
9
console.log(`a = ${a} and b = ${b} `); //a = 10 and b = 3 

10
11
/* But objects and arrays are not */
12
13
/*Let's start with objects */
14
15
let user = {
16
  name: "Bob",
17
  age: 22,
18
  job: "None"
19
}
20
21
active_user = user;
22
23
active_user.name = "Tim";
24
25
//Both the objects have the same value

26
console.log(active_user); // {"name":"Tim","age":22,"job":"None"} 

27
28
console.log(user); // {"name":"Tim","age":22,"job":"None"} 

29
30
/* Now for arrays */
31
32
let usersId = [1,2,3,4,5]
33
34
let usersIdDup = usersId;
35
36
usersIdDup.pop();
37
38
console.log(usersIdDup); //[1,2,3,4]

39
console.log(usersId); //[1,2,3,4]

Để làm cho các đối tượng không thay đổi được, hãy sử dụng phương thức Object.assign để tạo một phương thức mới hoặc toán tử spread mới.

1
let user = {
2
  name: "Bob",
3
  age: 22,
4
  job: "None"
5
}
6
7
active_user = Object.assign({}, user, {name:"Tim"})
8
9
console.log(user); //{"name":"Bob","age":22,"job":"None"} 

10
console.log(active_user); //{"name":"Tim","age":22,"job":"None"} 

Redux là gì?

Redux được định nghĩa chính thức như sau:

Redux là một container của trạng thái có thể dự đoán được cho các ứng dụng JavaScript.

Mặc dù định nghĩa này mô tả chính xác Redux nhưng rất dễ lầm lẫn khi bạn hình dung lớn hơn về Redux lần đầu tiên. Nó có rất nhiều mảnh ghép đang di chuyển mà bạn cần để ghép lại với nhau. Nhưng một khi bạn làm được, tôi hứa rằng bạn sẽ bắt đầu yêu Redux.

Redux là một thư viện quản lý nhà nước mà bạn có thể kết nối với bất kỳ thư viện JavaScript nào, và không chỉ là React. Tuy nhiên, nó hoạt động rất tốt với React vì tính chất chức năng của React. Để hiểu điều này tốt hơn, chúng ta hãy nhìn vào trạng thái.

Any change in the state rerenders the component and any user interaction updates the stateAny change in the state rerenders the component and any user interaction updates the stateAny change in the state rerenders the component and any user interaction updates the state

Như bạn có thể thấy, trạng thái của một component xác định sẽ hiển thị cái gì và cách hoạt động như thế nào. Ứng dụng có một trạng thái ban đầu và mọi tương tác của người dùng sẽ kích hoạt action cập nhật trạng thái. Khi trạng thái được cập nhật, trang được hiển thị lại.

Với React, mỗi component có một trạng thái cục bộ có thể truy xuất từ bên trong component đó, hoặc bạn có thể truyền chúng như các props đến các component con. Chúng tôi thường sử dụng trạng thái để lưu:

  1. Trạng thái giao diện người dùng và dữ liệu chuyển tiếp. Gồm một danh sách các yếu tố giao diện người dùng cho trình đơn điều hướng hoặc dữ liệu nhập vào biểu mẫu trong một component được kiểm soát.
  2. Trạng thái ứng dụng chẳng hạn như dữ liệu lấy được từ máy chủ, trạng thái đăng nhập của người dùng, v.v.

Việc lưu trữ dữ liệu ứng dụng trong trạng thái của một thành phần vẫn ổn khi bạn có một ứng dụng React cơ bản với một số component.

React Project with a few componentsReact Project with a few componentsReact Project with a few components
Phân cấp component của một ứng dụng cơ bản

Tuy nhiên, hầu hết các ứng dụng thực tế sẽ có nhiều tính năng và component hơn. Khi số lượng các cấp trong cấu trúc phân cấp component gia tăng, việc quản lý trạng thái phát sinh vấn đề.

Component hierarchy of a chat application using ReactComponent hierarchy of a chat application using ReactComponent hierarchy of a chat application using React
Minh hoạ một ứng dụng có quy mô trung bình

Tại sao bạn nên sử dụng Redux?

Đây là một kịch bản rất có thể xảy ra bạn có thể gặp phải khi làm việc với React.

  1. Bạn đang xây dựng một ứng dụng có quy mô trung bình, và bạn chia các component thành những component nhỏ: smart và dumb.
  2. Các smart component xử lý trạng thái và sau đó truyền chúng xuống các dumb component. Chúng chịu trách nhiệm gọi API, lấy dữ liệu từ nguồn dữ liệu, xử lý dữ liệu và sau đó thiết lập các trạng thái. Các smart component nhận được props và trả về phần hiển thị UI (giao diện người dùng).
  3. Khi bạn sẵn sàng viết một component mới, không phải lúc nào bạn cũng biết rõ phải đặt trạng thái ở đâu. Bạn có thể để trạng thái (state) trở thành một phần của container, container này là một cấp cao trực tiếp của component loại presentational. Tốt hơn, bạn có thể di chuyển trạng thái lên cao hơn trong cấu trức phân cấp để trạng thái có thể truy xuất tới nhiều component kiểu presentational.
  4. Khi ứng dụng tăng trưởng, bạn thấy rằng trạng thái nằm rải rác khắp mọi nơi. Khi một component cần truy cập vào trạng thái mà nó không có quyền truy cập ngay lập tức, bạn sẽ cố gắng đưa trạng thái lên component cao cấp gần nhất với nó.
  5. Sau khi liên tục tái cấu trúc và dọn dẹp, bạn có kết quả là hầu hết các trạng thái đang có vị trí ở trên cùng của cấu trúc phân cấp cho component.
  6. Sau cùng, bạn quyết định đó là một ý hay khi để component ở trên cùng xử lý trạng thái toàn cục và sau đó chuyển mọi thứ xuống dưới. Những component khác có thể đăng ký props mà chúng cần và bỏ qua những props còn lại.

Đây là điều mà cá nhân tôi đã trải nghiệm với React và rất nhiều nhà phát triển khác sẽ đồng ý. React là một thư viện xem, và nó không phải là công việc của React để quản lý nhà nước một cách cụ thể. Những gì chúng tôi đang tìm kiếm là nguyên tắc Separation of Concerns.

Redux giúp bạn tách trạng thái ứng dụng khỏi React. Redux tạo một store toàn cục nằm trên cấp cao nhất của ứng dụng của bạn và cung cấp trạng thái cho tất cả các component khác. Khác với Flux, Redux không có nhiều đối tượng store Toàn bộ trạng thái của ứng dụng nằm trong đối tượng store đó và bạn có khả năng chuyển đổi view layer bằng một thư viện mà vẫn giữ nguyên đối tượng store.

Các component sẽ hiển thị lại mỗi khi store được cập nhật, cũng ít ảnh hưởng đến hiệu suất. Là tin tốt, và điều này kèm theo rất nhiều lợi ích. Bạn có thể xem tất cả các component React của bạn là dumb component, và React chỉ có thể tập trung vào khía cạnh view của mọi thứ.

Bây giờ chúng ta đã biết tại sao Redux lại hữu dụng, hãy đi sâu vào kiến ​​trúc Redux.

Kiến trúc Redux

Khi bạn đang học Redux, có một vài khái niệm cốt lõi mà bạn cần làm quen. Hình ảnh dưới đây mô tả kiến ​​trúc Redux và cách mọi thứ được kết nối với nhau.

Getting started with Redux Redux ArchitectureGetting started with Redux Redux ArchitectureGetting started with Redux Redux Architecture
Ngắn gọn về Redux

Nếu bạn quen thuộc với Flux, thì một số yếu tố có thể trông quen thuộc. Nếu không quen thuộc cũng không sao vì chúng ta sẽ tìm hiểu mọi thứ từ mức cơ bản. Trước tiên, hãy chắc rằng bạn đã cài đặt redux:

1
npm install redux

Sử dụng create-react-app hoặc cấu hình webpack yêu thích của bạn để thiết lập máy chủ phát triển. Vì Redux là một phần quản lý trạng thái riêng biệt, chúng tôi sẽ không kết nối với React. Vậy hãy xoá bỏ các nội dung của index.js, và chúng ta sẽ thử nghiệm với Redux trong phần còn lại của hướng dẫn này.

Store

Store là một đối tượng JavaScript lớn có nhiều cặp khoá-giá trị (key-value) đại diện cho trạng thái hiện tại của ứng dụng. Không giống như đối tượng trạng thái trong React được dàn trải trên các component khác nhau, chúng ta chỉ có một store. Store cung cấp trạng thái ứng dụng và mỗi khi trạng thái cập nhật, view được tải lại.

Tuy nhiên, bạn không bao giờ có thể biến đổi hoặc thay đổi store. Thay vào đó, bạn tạo những phiên bản mới của store.

1
(previousState, action) => newState

Bởi vì điều này, bạn có thể làm thời gian lướt qua tất cả các state kể từ khi ứng dụng được khởi động trên trình duyệt của bạn.

Cửa hàng có ba phương pháp để giao tiếp với phần còn lại của kiến ​​trúc. Chúng là:

  • Store.getState() - Để truy xuất cây trạng thái hiện tại của ứng dụng của bạn.
  • Store.dispatch(action) — Để kích hoạt một thay đổi trạng thái dựa trên một action. Tìm hiểu thêm về các action phía bên dưới.
  • Store.subscribe(listener) — Để nhận thấy bất kỳ thay đổi nào trong state. Nó sẽ được gọi mỗi khi một action được gửi đi.

Hãy tạo một store. Redux có phương thức createStore để tạo một store mới. Bạn cần phải truyền cho store một reducer, mặc dù chúng tôi không biết đó là gì. Vì vậy, tôi sẽ chỉ tạo ra một hàm có tên gọi là reducer. Bạn có thể tùy chọn chỉ định đối số thứ hai đặt trạng thái ban đầu của store.

src/index.js

1
import { createStore } from "redux";
2
// This is the reducer

3
const reducer = () => {
4
/*Something goes here */
5
}
6
7
//initialState is optional.

8
//For this demo, I am using a counter, but usually state is an object

9
const initialState = 0
10
const store = createStore(reducer, initialState);

Bây giờ chúng ta sẽ chờ đợi bất kỳ thay đổi nào trong store, và sau đó console.log() trạng thái hiện tại của store.

1
store.subscribe( () => {
2
    console.log("State has changed"  + store.getState());
3
})

Vậy chúng ta cập nhật store như thế nào? Redux có những action để thực hiện điều này.

Action và trình tạo ra action

Các action cũng là các đối tượng JavaScript đơn giản gửi thông tin từ ứng dụng của bạn đến store. Nếu bạn có một bộ đếm đơn giản với một nút tăng lên, nhấn nút này sẽ dẫn đến một action được kích hoạt trông giống như sau:

1
{
2
  type: "INCREMENT",
3
  payload: 1
4
}

Action là nguồn thông tin duy nhất cho store. Trạng thái của store chỉ thay đổi theo một action. Mỗi action phải có thuộc tính type mô tả những gì đối tượng action sẽ làm. Ngoài ra, cấu trúc của action hoàn toàn tùy thuộc vào bạn. Tuy nhiên, hãy để action luôn nhỏ gọn vì một action thể hiện số lượng thông tin tối thiểu cần thiết để chuyển đổi trạng thái của ứng dụng.

Trong ví dụ trên, thuộc tính type được xét thành "INCREMENT" và một thuộc tính bổ sung payload được kèm theo. Bạn có thể đổi thuộc tính payload sang tên khác có nghĩa hơn, hoặc trong trường hợp của chúng ta, hãy lược bỏ nó hoàn toàn. Bạn có thể gửi một action đến store như thế này.

1
store.dispatch({type: "INCREMENT", payload: 1});

Trong khi code Redux, bạn thường sẽ không sử dụng các action theo cách trực tiếp. Thay vào đó, bạn sẽ gọi các hàm trả về các action và các hàm này thường được gọi là action creator (trình tạo action). Đây là action creator cho action tăng lên mà chúng ta đã đề cập trước đó.

1
const incrementCount = (count) => {
2
  return {
3
    type: "INCREMENT",
4
    payload: count
5
  }
6
}

Vì vậy, để cập nhật trạng thái của bộ đếm, bạn cần phải gửi action incrementCount như sau:

1
store.dispatch(incrementCount(1));
2
store.dispatch(incrementCount(1));
3
store.dispatch(incrementCount(1));

Nếu bạn truy cập vào console của trình duyệt, bạn sẽ thấy rằng nó hoạt động, một phần. Chúng tôi nhận được kết quả undefined (không xác định) bởi vì chúng tôi chưa định nghĩa reducer.

Browser console returns undefined for getStateBrowser console returns undefined for getStateBrowser console returns undefined for getState

Vậy giờ chúng ta đã thảo luận action và store. Tuy nhiên, chúng tôi cần một cơ chế để chuyển đổi thông tin do action cung cấp và chuyển đổi trạng thái của store. Reducer thực hiện vai trò này.

Reducer

Một action mô tả vấn đề, và reducer chịu trách nhiệm giải quyết vấn đề. Trong ví dụ trước, phương thức incrementCount trả về một action cung cấp thông tin về kiểu thay đổi mà chúng ta muốn thực hiện cho trạng thái. Reducer sử dụng thông tin này để thực sự cập nhật trạng thái. Có một trọng điểm nổi bật trong các tài liệu mà bạn cần nhớ khi sử dụng Redux:

Với cùng đối số, một Reducer sẽ tính toán trạng thái tiếp theo và trả về nó. Không có gì bất ngờ. Không có hiệu ứng phụ. Không cần gọi API. Không có đột biến. Chỉ là một phép tính.

Điều này có nghĩa là reducer phải là một hàm thuần túy. Với một tập hợp dữ liệu nhập vào, nó luôn trả về cùng một kết quả. Ngoài việc đó, nó không làm thêm gì khác. Reducer cũng không phải là nơi dành cho các hiệu ứng phụ như gọi AJAX hoặc lấy dữ liệu từ API.

Hãy điền vào reducer cho bộ đếm của chúng ta.

1
// This is the reducer

2
3
const reducer = (state = initialState, action) => {
4
    switch (action.type) {
5
      case "INCREMENT":
6
	      return state + action.payload
7
	    default:
8
	      return state
9
  }
10
}

Reducer nhận hai đối số — trạng thái (state) và action — và nó trả về một trạng thái mới.

1
(previousState, action) => newState

Trạng thái nhận một giá trị mặc định initialState, giá trị này chỉ được sử dụng nếu giá trị của trạng thái là undefined (không xác định). Nếu không, giá trị thực của state sẽ được duy trì. Chúng tôi sử dụng switch đổi để chọn action phù hợp. Tải lại trình duyệt và kết quả diễn ra như mong đợi.

Hãy thêm một trường hợp cho DECREMENT, mà không có bộ đếm nào chưa hoàn thành.

1
// This is the reducer

2
3
const reducer = (state = initialState, action) => {
4
    switch (action.type) {
5
        case "INCREMENT":
6
	      return state + action.payload
7
        case "DECREMENT":
8
          return state - action.payload
9
	    default:
10
	      return state
11
  }
12
}

Đây là action creator.

1
const decrementCount = (count) => {
2
  return {
3
    type: "DECREMENT",
4
    payload: count
5
  }
6
}

Cuối cùng, gửi nó đến store.

1
store.dispatch(incrementCount(4)); //4

2
store.dispatch(decrementCount(2)); //2

Thế đấy!

Tóm tắt

Hướng dẫn này xem như một điểm khởi đầu cho việc quản lý trạng thái với Redux. Chúng tôi đã đề cập mọi thứ cần thiết để hiểu các khái niệm cơ bản của Redux như store, action và reducer. Phần cuối của hướng dẫn, chúng tôi cũng tạo ra một bộ đếm minh hoạ bằng redux. Dù nó không quá nhiều, nhưng chúng ta đã học được cách ghép những mảnh ghép của câu đố lại với nhau.

Vài năm qua, React đã trở nên phổ biến. Trên thực tế, chúng tôi có một vài sản phẩm có sẵn trên thị trường để bạn mua, xét duyệt, triển khai, v.v. Nếu bạn đang tìm kiếm tài nguyên bổ sung dành cho React, đừng chần chừ, hãy xem qua chúng.

Trong hướng dẫn tiếp theo, chúng ta sẽ tận dụng những điều được học ở đây để tạo ra một ứng dụng React với Redux. Hãy chú ý theo dõi đến lúc đó nhé. Chia sẻ suy nghĩ của bạn trong các phần bình luận.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.