What is a ‘thunk’
Đó là một tên đặc biệt cho một function được return bởi một function khác
Thunks là các hàm được trả về ở đâu đó trong phần thân của một hàm khác.
=> nếu 1 hàm có phần return là 1 function thì function đó gọi là thunk
Đối với Redux, thunk là 1 function có logic bên trong tương tác với [dispatch]
và [getState]
của Redux
Việc sử dụng thunks yêu cầu redux-thunk, tuy nhiên Redux tool kit đã tích hợp sẵn thunk cho nên ta không cần làm gì cả
cách viết
Hàm thunk có 2 đối số: dispatch và getState
const thunkFunction = (dispatch, getState) => { alert('that is thunk function!');
}
trong đó:
[getState]
dùng để truy cập data có trong store.[dispatch]
dùng để thay đổi data trong store
Vì là một tên đặc biệt cho một function được return bởi một function khác, nên cái "function" được gọi là "thunk function"
và "function khác" được gọi là "thunk action creator"
.
Cả 2 function thì có thể là function hoặc là hàm lambda đều được:
// thunkCreator is the "thunk action creator"
export function thunkCreator(todoId) { // thunkFunction is the "thunk function" return async function thunkFunction(dispatch, getState) { const response = await client.get(`/fakeApi/todo/${todoId}`) dispatch(todosLoaded(response.todos)) }
}
//code lambda, thunkCreator is the "thunk action creator"
export const thunkCreator = todoId => async dispatch => { const response = await client.get(`/fakeApi/todo/${todoId}`) dispatch(todosLoaded(response.todos))
}
Như vậy "thunk action creator" sẽ trả về "thunk function"
Hàm thunk không được gọi trực tiếp bằng code. Thay vào đó, chúng được chuyển đến store.dispatch()
:
store.dispatch(thunkFunction)
và cách call "thunk function" này thì ta sẽ phải dùng store.dispatch(thunkFunction), nên code sẽ như này:
const Component: React.FC = () => { const dispatch = useDispatch() const eventHandler = () => { dispatch(thunkCreator(todoId)) //....
Mặc dù thường được sử dụng để tìm nạp dữ liệu. Tuy nhiên, chúng có thể được sử dụng cho nhiều tác vụ khác nhau và có thể chứa cả logic đồng bộ và không đồng bộ và có thể gọi dispatch hoặc getState bất kỳ lúc nào.
thực chiến:
Lưu ý, bài viết này là mục số 4 trong tìm hiểu React Redux. Nó yêu cầu bạn đã đọc trước (hiểu biết cơ bản) về Redux Core, Redux Tool Kit, React-Redux.
Nguyên nhân là từ mục 4 hất về sau thuộc tìm hiểu nguyên nhân chạy chứ không phải tìm cách chạy
Kiểm tra hoạt động với setTimeout và lấy data trong store:
tách thunk ra 1 file mới (Thunk.ts chẳng hạn):
trong File này ta sẽ sử dụng setTimeout và cũng đọc data có trong Redux thử xem
import { AnyAction, Dispatch } from "@reduxjs/toolkit";
import { LoginFormFields } from "../../features/authentication/login/function";
import { login } from "../../services/authentication/AuthenticationSlice";
export function loginAsync(data: LoginFormFields) { return (dispatch: Dispatch<AnyAction>, getState: any) => { setTimeout(() => { const state: RootState = getState(); dispatch(login(data.id)); console.log(data); console.log(state); }, 1000); };
}
tại file này, [data: LoginFormFields]
được đưa vào từ thunk creator và không cần khai báo trong thunk function mà sử dụng được luôn
như mình đã nói, Hàm thunk có 2 đối số: dispatch
và getState
, chúng nó đều là function
dùng cái nào khì khai báo vào thì khai báo vào input, không thì không cần cho vào
function getState
trả về Object store
là Store tổng, nên xem data thì lại phải store.dataInside
theo vì store là mutable nên đọc lại data cũ sẽ không có cập nhật kể cả đã dispatch
data mới, muốn xem cập nhật thì phải đọc lại:
const before: RootState = getState();
dispatch(login(data.id));
const after: RootState = getState();
Thực hiện dispatch data nhưng thông qua Thunk function:
import { useAppDispatch } from "../../../redux/store";
import { loginAsync } from "../../../shared/utils/Thunk";
import { LoginFormFields } from "./function";
const dispatch = useAppDispatch();
const submitHandler = (data: LoginFormFields) => { dispatch(loginAsync(data));
}
Kiểm tra hoạt động với các logic không đồng bộ async/await:
export function userAsync(data: LoginFormFields) { return (dispatch: any, getState: any) => { return axios.get('http://localhost:3001/user').then( (response: AxiosResponse<Array<User>, void>) => response.data ) };
}
hiện tại mình đang trả về 1 Promise, cho nên bên code có thể dùng Promise.then(data => handler) để xử lý
hoặc có sẵn getState
và dispatch
thì lấy để update vào store
nếu muốn
chỗ này có thể có logic xử lý (trước/sau) khi call axios, khi đó hãy để function là async/await rồi xử lý:
export function userAsync(data: LoginFormFields) { return async(dispatch: any, getState: any) => { //xử lý trước let response: AxiosResponse<Array<User>, void> = await axios.get('http://localhost:3001/user'); //xử lý sau return response.data; };
}
Dispatch data trong Component
const userList: Promise<Array<User>> = dispatch(userAsync(data));
userList.then(data => console.log(data));
Các trường hợp sử dụng
Bởi vì thunks là một công cụ có mục đích chung có thể chứa logic tùy ý, nên chúng có thể được sử dụng cho nhiều mục đích khác nhau.
Các trường hợp sử dụng phổ biến nhất là:
- Thực thi logic bổ sung khi bất kỳ action nào được gửi đi (chẳng hạn như ghi nhật ký hành động và trạng thái)
- Thực hiện các request không đồng bộ hoặc logic không đồng bộ đơn giản đến vừa phải (Promises và async/await)
- Tạm dừng, sửa đổi, trì hoãn, thay thế hoặc tạm dừng các hành động đã gửi
- Viết logic cần gửi nhiều request liên tiếp hoặc theo thời gian (setTimeOut / setInterval)
- Viết logic cần truy cập getState để đưa ra quyết định trong một action nào đó
- Dạy dispatch cách chấp nhận các giá trị khác bên cạnh các plain object, chẳng hạn như các function và Promise
Thunks được sử dụng tốt nhất cho logic đồng bộ phức tạp và logic không đồng bộ đơn giản đến vừa phải, chẳng hạn như tạo một request AJAX và gửi các action dựa trên kết quả của AJAX.
middleware hoạt động thế nào
về cơ bản, mỗi khi dispatch một giá trị lên store, middleware sẽ kiểm tra xem nó có phải là 1 function hay không. Nếu không phải, cho gọi tới dispatch như bình thường. Nếu phải, nó sẽ thực hiện function đó, đợi kết quả trả về rồi dispatch giá trị trả về đó vào store.
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { This gets called for every action you dispatch. // If it's a function, call it. if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } // Otherwise, just continue processing this action as usual return next(action); };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
Tổng kết
- Thunk là function mà được trả về từ 1 function khác, function khác đó được gọi là thunk action creator
- sử dụng thunk cho phép redux hiểu và xử lý được với các dạng trả về khác không chỉ giới hạn tại plain object
- vì RTK cài đặt mặc định thunk và phát triển Query và Mutation cho nên vanilla thunk không còn xuất hiện nhiều nữa