React Native - Cơ bản về Redux Saga
Xin chào tất cả các bạn, bài viết này mình xin chia sẻ một chút kiến thức mình tìm hiểu được về Redux Saga, mong mọi người theo dõi.
Nếu ai đã từng tìm hiểu qua về redux thì sẽ biết giữa quá trình dispatch một action và reducer chúng ta thường sẽ gọi một API nào đó từ server trả về không ít người sẽ gặp trường hợp mà API chưa kịp trả về kết quả mà các dòng code tiếp theo đã được thực thi sẽ dẫn đến trường hợp không mong muốn, và để khác phục điều đó chúng ta có một thư viện giúp chúng ta xử lý các điều này dễ dàng hơn đó là Redux saga
. (trước khi được bài này thì các bạn nên hiểu qua một chút về redux và react).
1. Redux Saga là gì?
Redux-Saga là một thư viện redux middleware, giúp chúng ta quản lý những side effect trong ứng dụng redux trở nên một cách đơn giản hơn và dễ kiểm soát hơn.
Để hiểu được sự hoạt động của redux saga bạn cần hiểu một số khái niệm cơ bản như generator function.
Generator function là function có khả năng hoãn lại quá trình thực thi mà vẫn giữ nguyên được context. Hơi khó hiểu phải không, nói một cách đơn giản thì generator function là 1 function có khả năng tạm ngưng trước khi hàm kết thúc (khác với pure function khi được gọi sẽ thực thi hết các câu lệnh trong hàm), và có thể tiếp tục chạy tại một thời điểm khác. Chính chức năng mới này giúp ta giải quyết được việc bất đồng bộ, hàm sẽ dừng và đợi async chạy xong rồi tiếp tục thực thi. Chi tiết thì bạn có thể tham khảo tại đây
- Hãy nhình vào sơ đồ dưới đây:
Từ phía redux saga chúng ta gửi request đến Server để chúng ta lấy các API về sau đó ta có response trả về redux saga, sau khi có dữ liệu thì chúng ta sử dụng các effects, trong các effect này chúng ta có các hàm để dispatch sang phía reducers.
2. Các kiến thức cơ bản về Redux Saga
Redux saga cung cấp cho chúng ta một số method gọi là effect như sau:
Fork()
: sử dụng cơ chế non - blocking call trên functionCall()
: Gọi function. Nếu nó return về một promise, tạm dừng saga cho đến khi promise được giải quyếtTake()
: tạm dừng cho đến khi nhận được actionPut()
: Dùng để dispatch một actiontakeEvery()
: Theo dõi một action nào đó thay đổi thì gọi một saga nào đóakeLastest()
: Có nghĩa là nếu chúng ta thực hiện một loạt các actions, nó sẽ chỉ thực thi và trả lại kết quả của của actions cuối cùngyield()
: Có nghĩa là chạy tuần tự khi nào trả ra kết quả mới thực thi tiếpSelect()
: Chạy một selector function để lấy data từ state
3. Tạo project React Native sử dụng Redux Saga
Đầu tiên chúng ta khởi tạo project thông qua lệnh sau:
npx react-native init AwesomeApp
Bạn cần phải install react-native cli trước khi chạy lệnh trên nhé
Tiếp theo cần install các thư viện sau:
npm install redux
npm install redux-saga
npm install react-redux
Chúng ta sẽ cùng tạo 1 App counter đơn giản với 2 nút + - update giá trị khi người dùng click vào
Cấu trúc của project như sau:
Sau khi khởi tạo project và cài đặt một số thư viện liên quan chúng ta sẽ vào file AwesomeApp/index.js để cấu hình redux-saga vào trong project:
import React, {Component} from 'react';
import { AppRegistry } from 'react-native';
import {name as appName} from './app.json';
import { createStore, applyMiddleware } from 'redux';
import allReducers from './src/reducers';
import createSagaMiddleware from 'redux-saga';
import { Provider } from "react-redux";
import rootSaga from './src/sagas';
import CountrerComponent from './src/component/CountrerComponent';
const sagaMiddleware = createSagaMiddleware(); // Hàm này có nhiệm vụ tạo ra một middleware năm giữa action và reducer trong redux
const store = createStore(allReducers, applyMiddleware(sagaMiddleware));
const App = () => {
return (
<Provider store={store}>
<CountrerComponent></CountrerComponent>
</Provider>
)
}
sagaMiddleware.run(rootSaga); // Hàm này là chạy các saga
AppRegistry.registerComponent(appName, ()=>App);
Tạo lần lượt các thành phần chính của Redux Saga là reducer, saga, action ở trong src nhé
Actions
- actions/index.js
import { INCREMENT, DECREMENT } from "./actionType";
export const incrementAction = step =>{
return {
type: INCREMENT, step
}
}
export const decrementAction = step =>{
return {
type: DECREMENT, step
}
}
- action/actionType.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
Reducers
- reducers/counterReducer.js
import { DECREMENT, INCREMENT } from "../actions/actionType";
const initialState = {
count: 0
};
export default function(state = initialState, action){
console.log("Counter Reducer, " + action.step);
switch(action.type){
case INCREMENT:
return {
...state,
count: state.count + action.step
}
case DECREMENT:
return {
...state,
count: state.count - action.step
}
default: return initialState;
}
}
- reducers/index.js
import { combineReducers} from 'redux';
import counterReducer from './counterReducer';
const allReducers = combineReducers({
counterReducer
});
export default allReducers
Saga
- sagas/counterSaga.js
import { INCREMENT, DECREMENT } from "../actions/actionType";
import { takeEvery } from "redux-saga/effects";
function* increment(){
console.log("this is increment saga");
}
function* decrement(){
console.log("this is decrement saga");
}
export function* watchIncrement(){
yield takeEvery(INCREMENT, increment);
}
export function* watchDecrement(){
yield takeEvery(DECREMENT, decrement);
}
Khi action có type là DECREMENT, INCREMENT được gọi thì hàm decrement sẽ được thực thi và các side effect sẽ được xử lý ở trong này.
- sagas/index.js
import {all} from 'redux-saga/effects';
import {watchIncrement, watchDecrement} from './counterSaga';
export default function* rootSaga(){
yield all([
watchDecrement(), watchIncrement()
]);
}
Tại đây các saga sẽ được gom lại trong effect all để chạy.
Và cuối cùng là Counter Component
- component/CountrerComponent.js
import React, {Component} from "react";
import {Text, View, TouchableOpacity} from 'react-native';
import { connect } from "react-redux";
import { decrementAction, incrementAction } from "../actions";
class CounterComponent extends Component {
render(){
const {container, conainerButton, buttonStyle, text, textStyle} = styles;
return (
<View style={container}>
<View style={conainerButton}>
<TouchableOpacity style={buttonStyle} onPress={()=>this.props.onDecrement(1)}>
<Text> {"Decrement - "}</Text>
</TouchableOpacity>
<TouchableOpacity style={buttonStyle} onPress={()=>this.props.onIncrement(1)}>
<Text> {"Increment + "}</Text>
</TouchableOpacity>
</View>
<View style={textStyle}>
<Text style={text}>Count: {this.props.times}</Text>
</View>
</View>
)
}
}
const mapStateToProps = (state)=>{
return {
times: state.counterReducer ? state.counterReducer.count : 0
}
}
const mapDispatchToProps = (dispatch) =>{
return {
onIncrement: (step)=>{
dispatch(incrementAction(step));
},
onDecrement: (step)=>{
dispatch(decrementAction(step));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CounterComponent);
const styles = {
container: {
flex: 1,
backgroundColor: "#d3fee9",
justifyContent: "center"
},
conainerButton: {
flexDirection: "row",
justifyContent: "center"
},
buttonStyle: {
height: 50,
backgroundColor: "#8846a4",
margin: 24,
alignItems: "center",
justifyContent: "center",
padding: 16,
borderRadius: 5
},
textStyle: {
alignItems: "center"
},
text: {
color: "#111499",
fontSize: 20
}
};
Ưu điểm
- Do tách riêng side-effect ra khỏi action nên việc testing là dễ dàng hơn Redux-Thunk
- Giữ cho action pure synchronos theo chuẩn redux và loại bỏ hoàn toàn callback theo javascript truyền thống
Nhược điểm
- Function ko viết được dạng arrow-function.
- Phải hiểu về Generator function và các khái niệm trong saga pattern
Kết luận
Trên đây là một chút kiến thức mà mình tìm hiểu được về các thành phần trong redux-saga, rất mong được sự góp ý của mọi người. Cảm ơn mọi người đã theo dõi bài viết của mình