Thuật ngữ “thunk” ở trong lập trình có nghĩa là đoạn code gây ra một số tác vụ trì hoãn.
Trong Redux, thunk là một hàm cho phép bổ sung các logic tách biệt với UI layer. Các logic này có thể là các side effect (chẳng hạn như fetch data), dispatch nhiều action hoặc truy cập đến state của store.
Để sử dụng thunk, ta cần thêm “redux-thunk” middleware vào store. Nếu sử dụng Redux Toolkit thì không cần.
Writing Thunks
Một thunk function sẽ nhận vào hai đối số là phương thức dispatch
và getState
.
Ví dụ:
const thunkFunction = (dispatch, getState) => {
// logic here that can dispatch actions or read state
}
Logic ở trong thunk có thể là đồng bộ hoặc bất đồng bộ và ta có thể gọi sử dụng dispatch
hoặc getState
bất cứ lúc nào.
Chúng ta cũng có thể dùng một hàm để tạo ra thunk. Ta gọi hàm đó là thunk action creator, ví dụ:
// fetchTodoById is the "thunk action creator"
export function fetchTodoById(todoId) {
// fetchTodoByIdThunk is the "thunk function"
return async function fetchTodoByIdThunk(dispatch, getState) {
const response = await client.get(`/fakeApi/todo/${todoId}`)
dispatch(todosLoaded(response.todos))
}
}
Thunk action creator và thunk cũng có thể là arrow function:
export const fetchTodoById = (todoId) => async (dispatch) => {
const response = await client.get(`/fakeApi/todo/${todoId}`)
dispatch(todosLoaded(response.todos))
}
Thunk sẽ được dispatch thông qua thunk action creator, tương tự như khi ta dispatch action bằng cách truyền vào action creator:
function TodoComponent({ todoId }) {
const dispatch = useDispatch()
const onFetchClicked = () => {
dispatch(fetchTodoById(todoId))
}
}
Minh họa quá trình gọi API khi sử dụng thunk:
Tip
Thunk action creator và thunk thường được viết ở trong file chứa slice.
Thunk Usage Patterns
Dispatching Actions
Chúng ta có thể dùng thunk để dispatch nhiều action cùng một lúc hoặc thậm chí là dispatch các thunk khác.
Ví dụ bên dưới dispatch một action và một thunk:
function complexSynchronousThunk(someValue) {
return (dispatch, getState) => {
dispatch(someBasicActionCreator(someValue))
dispatch(someThunkActionCreator())
}
}
Accessing State
Không giống như các component, thunk có quyền truy cập đến phương thức getState
và có thể gọi sử dụng phương thức này bất cứ lúc nào. Tính năng này hữu ích khi ta cần xử lý một số logic dựa trên state hiện tại, ví dụ:
const MAX_TODOS = 5
function addTodosIfAllowed(todoText) {
return (dispatch, getState) => {
const state = getState()
// Could also check `state.todos.length < MAX_TODOS`
if (selectCanAddNewTodo(state, MAX_TODOS)) {
dispatch(todoAdded(todoText))
}
}
}
Do state được cập nhật ngay lập tức nên ta có thể gọi getState
sau khi dispatch để lấy ra state mới:
function checkStateAfterDispatch() {
return (dispatch, getState) => {
const firstState = getState()
dispatch(firstAction())
const secondState = getState()
if (secondState.someField != firstState.someField) {
dispatch(secondAction())
}
}
}
Trong trường hợp ta cần lấy ra state của nhiều slice khác nhau, ta cũng có thể sử dụng thunk:
function crossSliceActionThunk() {
return (dispatch, getState) => {
const state = getState()
// Read both slices out of state
const { a, b } = state
// Include data from both slices in the action
dispatch(actionThatNeedsMoreData(a, b))
}
}
Async Logic and Side Effects
Khi thực hiện gửi request ở trong thunk, ta cần dispatch các action trước và sau khi request được xử lý nhằm kiểm soát loading state của request đó.
Thường thì ta sẽ dispatch “pending” action trước khi gửi request. Nếu request thành công thì ta sẽ dispatch “fulfilled” action. Còn nếu request thất bại thì ta sẽ dispatch “rejected” action.
Ví dụ:
function fetchData(someValue) {
return (dispatch, getState) => {
dispatch(requestStarted())
myAjaxLib.post("/someEndpoint", { data: someValue }).then(
(response) => dispatch(requestSucceeded(response.data)),
(error) => dispatch(requestFailed(error.message))
)
}
}
Using createAsyncThunk
Thay vì tạo ra 3 action cho 3 trạng thái của request một cách thủ công và dispatch chúng ở trong thunk, ta có thể sử dụng phương thức createAsyncThunk
của Redux Toolkit để tự động sinh ra các action cũng như là lược bỏ các dispatch ở trong thunk function.
import { createAsyncThunk } from "@reduxjs/toolkit"
Phương thức này nhận vào hai đối số:
- Một action type string dùng để tạo các action type tương ứng với 3 trạng thái của request (
pending
,fulfilled
vàrejected
) - Một “payload creation callback” chứa các đoạn code bất đồng bộ và trả về một Promise.
Ví dụ:
export const fetchSneakers = createAsyncThunk("sneakers/fetchSneakers", async () => {
const response = await axios.get(URL)
return response.data
})
Giá trị trả về của createAsyncThunk
là một async thunk.
Payload creation callback cũng nhận vào hai đối số:
arg
: là đối số truyền vào async thunk khi dispatch.thunkAPI
: là một đối tượng gồm nhiều thuộc tính. Trong số đó có phương thứcrejectWithValue
giúp trả về một rejected response.
Ví dụ:
export const fetchSneakerById = createAsyncThunk("sneakers/fetchSneakerById", async (sneakerId, thunkAPI) => {
try {
const response = await axios.get(`URL/${sneakerId}`)
return response.data
} catch(error) {
return thunkAPI.rejectWithValue("Something went wrong: ", error.message)
}
})
Seealso
Thêm vào các reducer cho từng trạng thái của request khi tạo slice như sau:
const sneakersSlice = createSlice({
name: "sneakers",
initialState: {
sneakers: [],
isLoading: "false",
},
reducers: {
// omit reducer cases
},
extraReducers: {
[fetchSneakers.pending]: (state) => {
state.isLoading = "true"
},
[fetchSneakers.fulfilled]: (state, action) => {
state.sneakers = action.payload
state.isLoading = "false"
},
[fetchSneakers.rejected]: (state, action) => {
console.error(action.payload)
state.isLoading = "false"
}
}
})
Lưu ý là ta cần phải export slice reducer để thêm vào store.
export default sneakersSlice.reducer
Dispatch async thunk ở trong component như sau:
<button onClick={() => dispatch(fetchSneakers())}>
Fetch sneakers
</button>
Cũng có thể dispatch async thunk thông qua store:
import { getCartItems } from "../features/cart/cartSlice"
export const store = configureStore({
// ...
})
store.dispatch(getCartItems())
Related
list
from [[Writing Logic with Thunks]]
sort file.ctime asc