9
REACT REDUX
Trong chương 6, chúng ta đã biết State là một trong những dạng dữ
liệu quan trọng trong dự án React Native. State giống như một kho
lưu trữ dữ liệu cho các component trong ReactJS. Nó được sử dụng
để cập nhật component khi người dùng thực hiện một số hành động
như nhấp vào nút, nhập văn bản, ẩn hiện view nào đó,v.v...
Hiện tại chúng ta mới biết cách khai báo và sử dụng state trực tiếp
ngay trong từng component, thông qua sử dụng useState hook. Với
cách làm này thì không có gì sai và mọi người cũng vẫn đang làm như
vậy.
Tuy nhiên, nếu state đó được sử dụng ở nhiều component khác
nhau, nhiều màn hình trong ứng dụng cùng dùng chung một state.
Lúc này bạn phải làm sao?
Mình lấy ví dụ điển hình đó là trường hợp bạn cần lưu trạng thái đăng
nhập của một tài khoản. Trạng thái đăng nhập sẽ ảnh hưởng tới toàn
VNTALKING.COM 133
bộ các màn hình trong ứng dụng, bạn có thể hiểu nó như một dữ liệu
global cho toàn ứng dụng vậy.
Tiếp tục nhé! Vấn đề bắt đầu khó khăn hơn khi ứng dụng ngày càng
có nhiều state dạng global như vậy. Nào là trạng thái đăng nhập,
theme (dark mode, light mode), các cấu hình chung của ứng
dụng,v.v... Chúng ta cần phải có một giải pháp để quản lý các state
global này. Trong React Native, giải pháp phổ biến nhất là sử dụng
Redux.
Khái niệm chung Redux
Redux là một công cụ quản lý state. Mặc dù nó được sử dụng nhiều
với React. Nên nhiều người nhầm tưởng Redux là thư viện dành riêng
cho React. Nhưng thực tế thì nó có thể được sử dụng với bất kỳ
framework hoặc thư viện JavaScript nào khác.
Với Redux, các state của ứng dụng được giữ trong một "store" và các
component đều có thể truy cập vào store này để lấy state.
Về cơ bản thì Redux được xây dựng có nhiều điểm chung với Flux của
Facebook.
Redux được ra đời từ việc lấy cảm hứng từ kiến trúc Flux của
Facebook. Tuy nhiên, cách tiếp cận của Redux lại hơi khác Flux một
chút. Để rõ hơn phần này, chúng ta cùng xem nguyên lý hoạt động cơ
bản của Redux nhé.
VNTALKING.COM 134
Nguyên lý hoạt động Redux
Để sử dụng Redux dễ dàng hơn, chúng ta cần phải “thông suốt” được
3 nguyên lý chính của Redux:
• Single store: Nguyên tắc đầu tiên của Redux là tất cả các state
được chứa trong một store duy nhất, store này thường là một
Object.
• Immutable: State của ứng dụng là bất biến (Immutable). Điều
này có nghĩa là Object để lưu trữ state không cho phép chỉnh
sửa, cập nhật dữ liệu trực tiếp từ bất kỳ component nào. Để cập
nhật state trong store từ các component, chúng ta bắt buộc
phải tuân theo quy trình của Redux, đó là gửi các actions và để
các reducer function cập nhật state.
• Pure functions: Tất cả các functions để cập nhật hay tạo mới
state (Redux gọi các function này là reducer) phải là các pure
function. Mình hiểu đơn giản pure function là một hàm mà khi
các params đầu vào không thay đổi thì kết quả return luôn luôn
không thay đổi, dù hàm đó có thực thi bao nhiêu lần đi chăng
nữa.
Các thành phần của React Redux
Từ những nguyên lý trên, chúng ta bắt đầu tìm hiểu cụ thể trong
Redux có những thành phần gì? vai trò và nhiệm vụ của chúng như
nào.
VNTALKING.COM 135
Hình dưới đây minh họa cho toàn bộ cơ chế hoạt động của Redux:
Mình sẽ giải thích chi tiết hơn các thành phần trong hình trên:
“Store” có nhiệm vụ lưu trữ toàn bộ state của ứng dụng, nó được tạo
thông qua các reducer.
Để tạo store, chúng ta sử dụng hàm createStore(). Ví dụ như đoạn mã
dưới đây:
// Khởi tạo store
const store = createStore(loggeduser);
Chúng ta chỉ nên tạo duy nhất một store để dùng chung cho toàn
ứng dụng thôi nhé.
“Actions” thực chất là đối tượng Javascript được gửi đi bằng phương
thức dispatch() của React Native. Những actions này bắt buộc phải có
thuộc tính type để xác định loại action được thực hiện.
VNTALKING.COM 136
Giá trị của thuộc tính type là duy nhất, tương ứng với
action đó. Bạn không nên để trùng nhau vì sau này sẽ phát
sinh nhiều lỗi rất khó chịu. Kiểu như dispatch() một action
mà ứng dụng lại cập nhật trạng thái lung tung, không đúng
ý của mình.
Trong đối tượng Action này, bạn hoàn toàn có thể gửi thêm thông tin
thông qua thuộc tính payload. Ví dụ: thông tin là các giá trị mới của
state cần cập nhật chẳng hạn.
Dưới đây là đoạn mã minh họa cho một Action:
// Định nghĩa một action kèm thông tin username và password
const setUser = (user) => {
return {
type: "LOGIN",
payload: {
username : user.name,
password : user.password
}
}
}
“Reducers” là pure function để cập nhật, thay đổi giá trị state được
lưu trong store. Các Reducers sẽ căn cứ vào action và payload được
truyền vào mà trả về state với giá trị mới.
Ví dụ một đoạn mã dưới đây cách Reducers cập nhật state lưu thông
tin đăng nhập (username và password).
VNTALKING.COM 137
// Reducer
const initialState = {
username: '',
password: ''
};
const loggeduser = (state = initialState, action) => {
switch (action.type) {
case "LOGIN":
return {
username : action.payload.username,
password : action.payload.password
};
default:
return state;
}
};
Ok, Lý thuyết về Redux cơ bản chỉ như vậy thôi, chúng ta cũng không
cần phài đào sâu quá làm gì, mình biết những cái cần thiết trước và
ứng dụng được trong dự án là được.
Phần tiếp theo, chúng ta cùng nhau thực hành một ví dụ nhỏ về cách
cài đặt, cấu hình để sử dụng Redux trong dự án React Native nhé.
Cài đặt và cấu hình Redux cho dự án
Để cài đặt Redux cho dự án React Native, chúng ta cũng thực hiện
giống như cài đặt một dependencies bình thường khác.
npm install redux
npm install react-redux
VNTALKING.COM 138
Sau khi cài đặt xong thì chúng ta tiến hành cấu hình cơ bản để Redux
hoạt động được với dự án. Bạn tải mã nguồn starter cho phần này về
rồi cùng mình thực hành nhé.
Tải mã nguồn trước khi thực hành
Hãy sử dụng mã nguồn này trước khi vào thực hành
Chuong9-Redux-Starter.zip
Trong ví dụ này, chúng ta sẽ tạo một ứng dụng tăng giảm biến đếm
và hiển thị ra ngoài màn hình. Ví dụ này đã được sử dụng trong
Chương 6 - phần State. Tuy nhiên, ở chương 6, chúng ta thực hiện lưu
state trực tiếp trong component. Đến chương này, chúng ta sẽ thử
dùng Redux để lưu và quản lý state trong store.
Hình ảnh ứng dụng sau khi hoàn thành:
VNTALKING.COM 139
Trước hết, chúng ta dựng lại giao diện ứng dụng giống như trên đã
nhé (bỏ qua xử lý logic).
screens/Home.js
import React, { useState } from 'react';
import { StyleSheet, View, Text, TouchableOpacity} from 'react-
native';
const Home = () => {
const handleIncreament = () => {
console.log("Tăng điểm")
};
const handleDecreament = () => {
console.log("Giảm điểm")
};
return (
<View style={{flex: 1}}>
<View style={{alignItems: 'center',
backgroundColor:"#cc3333"}}>
<Text style={styles.textHeader}>Giới thiệu sử dụng
Redux</Text>
<Text style={{color: "#FFFFFF"}}>VNTALKING.COM</Text>
</View>
<View style={styles.container}>
<Text style={styles.title_text}>Bộ đếm 2.0</Text>
<Text style={styles.counter_text}>0</Text>
<TouchableOpacity onPress={handleIncreament}
style={styles.btn}>
<Text style={styles.btn_text}> Tăng điểm </Text>
</TouchableOpacity>
<TouchableOpacity
onPress={handleDecreament}
style={{ ...styles.btn, backgroundColor: '#6e3b3b'
}}>
<Text style={styles.btn_text}> Giảm điểm </Text>
</TouchableOpacity>
</View>
</View>
)
VNTALKING.COM 140
}
const styles = StyleSheet.create({
textHeader: {
fontSize: 18,
fontWeight: "bold",
color: "#FFFFFF"
},
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
flexDirection: 'column',
padding: 50,
},
title_text: {
fontSize: 40,
fontWeight: '900',
marginBottom: 55,
},
counter_text: {
fontSize: 35,
fontWeight: '900',
margin: 15,
color: '#D64545'
},
btn: {
backgroundColor: '#086972',
padding: 10,
margin: 10,
borderRadius: 10,
},
btn_text: {
fontSize: 23,
color: '#fff',
},
})
export default Home;
Còn file App.js sẽ cấu hình để trỏ sang màn hình Home.
VNTALKING.COM 141
App.js
import Home from './screens/Home';
import React from 'react';
export default function App() {
return <Home />;
}
Bước tiếp theo, chúng ta sẽ định nghĩa các action tương ứng với hai
hành động khi nhấn nút “tăng điểm” và “giảm điểm”.
Để cho mã nguồn dự án nó gọn gàng, mình sẽ tạo thư mục
“redux” dành riêng cho toàn bộ code liên quan tới redux.
Tạo file countAction.js trong thư mục redux và định nghĩa action
tương ứng như sau:
redux/countAction.js
export const increment = () => {
return {
type: 'INCRESE',
};
};
export const decrement = () => {
return {
type: 'DECRESE',
};
};
Sau đó, chúng ta tạo countReducer.js (vẫn để trong thư mục redux)
VNTALKING.COM 142
redux/countReducer.js
const initialState = {
count: 0,
};
export default (state = initialState, action) => {
switch (action.type) {
case 'INCRESE':
return {
...state,
count: state.count + 1,
};
case 'DECRESE':
return {
...state,
count: state.count - 1,
};
default:
return state;
}
};
Đây là reducer dùng để cập nhật state lưu giữ trạng thái biến đếm,
với giá trị mặc định là 0, tức là mỗi lần mở ứng dụng, ứng dụng sẽ
hiển thị giá trị bắt đầu là số 0.
Bước tiếp theo, chúng ta tạo một store để lưu giữ state, cụ thể là
“count” state.
Tạo một file store.js trong thư mục redux:
redux/store.js
import { createStore, combineReducers} from 'redux';
import CountReducer from './countReducer';
const rootReducer = combineReducers({
VNTALKING.COM 143
count: CountReducer,
});
export const store = createStore(rootReducer);
Gần xong rồi! Sau khi các món ăn đã chuẩn bị xong hết rồi, rượu
ngon cũng đã sẵn sàng, giờ là đi mời các bạn hiền tới nhậu thôi
Đùa thôi, (viết sách nhiều khi bị tẩu hỏa nhập ma, mong bạn thông
cảm!). Bước tiếp theo là chúng ta thực hiện kết nối store vừa tạo ở
trên tới ứng dụng.
Chúng ta quay trở lại file App.js và thay đổi code một chút:
App.js
import Home from './screens/Home';
import React from 'react';
import { Provider } from 'react-redux';
import { store } from './redux/store';
export default function App() {
return (
<Provider store={store}>
<Home />
</Provider>
);
}
Trong đoạn code trên, mình bọc toàn bộ màn hình ứng dụng vào
trong Provider.
VNTALKING.COM 144
Như vậy là đã kết nối xong rồi đấy. Giờ là lúc sử dụng thôi. Quay lại
màn hình Home, mình sẽ chỉnh sửa code một chút để nó lưu giá trị
vào store, sau đó lấy ra để hiển thị lên màn hình.
screens/Home.js
import React from 'react';
import { StyleSheet, View, Text, TouchableOpacity} from 'react-
native';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from '../redux/countAction';
const Home = () => {
const dispatch = useDispatch();
const count = useSelector((store) => store.count.count);
const handleIncreament = () => {
console.log("Tăng điểm");
dispatch(increment());
};
const handleDecreament = () => {
console.log("Giảm điểm");
dispatch(decrement());
};
return (
<View style={{flex: 1}}>
<View style={{alignItems: 'center',
backgroundColor:"#cc3333"}}>
<Text style={styles.textHeader}>Giới thiệu sử dụng
Redux</Text>
<Text style={{color: "#FFFFFF"}}>VNTALKING.COM</Text>
</View>
<View style={styles.container}>
<Text style={styles.title_text}>Bộ đếm 2.0</Text>
<Text style={styles.counter_text}>{count}</Text>
<TouchableOpacity onPress={handleIncreament}
style={styles.btn}>
<Text style={styles.btn_text}> Tăng điểm </Text>
</TouchableOpacity>
VNTALKING.COM 145
<TouchableOpacity
onPress={handleDecreament}
style={{ ...styles.btn, backgroundColor: '#6e3b3b'
}}>
<Text style={styles.btn_text}> Giảm điểm </Text>
</TouchableOpacity>
</View>
</View>
)
}
Ở đây, chúng ta sử dụng các hooks được cung cấp bởi thư viện react-
redux để gửi action và truy cập vào store. Cụ thể là hai hooks sau:
• useSelector: Giúp truy xuất vào store để lấy state.
• useDispatch: Giúp gửi action để kích hoạt thay đổi state
Các bạn chạy ứng dụng để tận hưởng thành quả nhé:
VNTALKING.COM 146
Tải mã nguồn tham khảo
Hãy thực hành chạy thử với mã nguồn đầy đủ của phần này nhé
Chuong9-Redux-Counter-Final.zip
Tổng kết
Qua tìm hiểu lý thuyết và thực hành ví dụ trên, bạn thấy Redux này
thế nào? Việc quản lý State qua redux nó có ích lợi gì không?
Có thể bạn sẽ bật ra ngay ý nghĩ kiểu: Việc để state trong store tập
trung như này chả khác gì khi vừa nhận lương tháng, vội vàng cất vào
két sắt, xong lại vội vàng mở két lấy ra để đi uống bia cả. Thực tế thì
bạn nói cũng có ý đúng!
Quan điểm cá nhân: với những trường hợp state nào mà không cần
chia sẻ cho nhiều màn hình thì đúng là không cần phải quản lý tập
trung, gây phức tạp thêm tình hình. Tuy nhiên, nếu bạn là người ngăn
nắp, thích gọn gàng thì việc quản lý tập trung state về một mối cũng
có nhiều điểm lợi thế. Đặc biệt là khoản debug ứng dụng qua React
Dev Tools.
VNTALKING.COM 147