-
Là một thư viện JS dùng để quản lý và cập nhật
state
của ứng dụng -
Redux hoạt động như một kho lưu trữ tập trung cho các
state
mà được sử dụng ở nhiềucomponents
hay nhiều nơi khác nhau trong ứng dụng -
Redux là một pattern (1 khuôn mẫu): có nhiều khái niệm, quy tắc mới phải tuân theo
-
Quản lý Global state
- Các components tại mọi nơi trong ứng dụng có thể truy xuất và cập nhật
- Giải quyết vấn đề của React khi muốn truyền dữ liệu vào các cấp con cháu
-
Dễ dàng debug (Redux dev-tool)
-
Xử lý caching dữ liệu từ server (lưu các dữ liệu từ server để các lần tải sau nhanh hơn)
- Cấu hình Redux phức tạp
- Phải cài đặt thủ công nhiều packages để có thể hoạt động hiệu quả
- Redux yêu cầu nhiều
boilerplate code
(code bị lặp đi lặp lại nhiều lần)giải quyết bằng cách sử dụng Redux Toolkit
Redux sẽ rất hữu dụng đối với các trường hợp sau:
- Dự án có số lượng lớn
state
và cácstate
được sử dụng ở nhiều nơi State
được cập nhật thường xuyên- Logic code cập nhật các
state
phức tạp - Ứng dụng có số lượng code trung bình hoặc lớn và có nhiều người làm chung
- Cần debug và muốn xem cách
state
được cập nhật tại bất kì khoảng thời gian nào
function Counter() {
// State: a counter value
const [counter, setCounter] = useState(0)
// Action: code that causes an update to the state when some thing happens
const increment = () => {
setCounter((prevCounter) => prevCounter + 1)
}
// View: the UI definition
return (
<div>
Value: {counter}
<button onClick={increment}>Increment</button>
</div>
)
}
Gây ra bug:
- UI không được cập nhật một cách chính xác để hiển thị các giá trị mới nhất
- Phá vỡ khả năng debug, xem được các giá trị
state
ở từng thời điểm khác nhau nó được cập nhật như thế nào
/**
* Ví dụ về mutation (thay đổi giá trị obj, array)
* KHÔNG NÊN DÙNG TRONG REDUX
*/
const obj = { a: 1, b: 2 }
obj.b = 3
const arr = ['a', 'b']
arr.push('c')
arr[1] = 'd'
/**
* Ví dụ về Immutaiton (không thay đổi giá trị obj, array)
* NÊN DÙNG TRONG REDUX
*/
const obj = {
a: { c: 3 },
b: 2,
}
const obj2 = {
//copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}
const arr = ['a', 'b']
// create a new copy of arr, with 'c' appended to the end
const arr2 = arr.concat('c')
// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
// Redux Core
/**
* Các giá trị state mới luôn luôn được tính toán dựa trên giá trị của state trước đó
* Không bao giờ được thay đổi các giá trị state hiện tại -> thực hiện code Immuation
*/
const initState = {
search: '',
status: 'All',
priorities: [],
}
const filtersReducer = (state = initState, action) => {
switch (action.type) {
case 'filters/searchFilterChange':
return {
...state,
search: action.payload,
}
case 'filters/statusFilterChange':
return {
...state,
status: action.payload,
}
case 'filters/prioritiesFilterChange':
return {
...state,
priorities: action.payload,
}
default:
return state
}
}
export default filtersReducer
// action.js
export const searchFilterChange = (text) => {
return {
type: 'filters/searchFilterChange',
payload: text,
}
}
export const statusFilterChange = (status) => {
return {
type: 'filters/statusFilterChange',
payload: status,
}
}
export const priorityFilterChange = (priorities) => {
return {
type: 'filters/prioritiesFilterChange',
payload: priorities,
}
}
// Redux Toolkit
import { createSlice } from '@reduxjs/toolkit'
export default createSlice({
name: 'filters',
initialState: {
search: '',
status: 'All',
priorities: [],
},
reducers: {
searchFilterChange: (state, action) => {
// mutation || IMMER
state.search = action.payload
},
statusFilterChange: (state, action) => {
state.status = action.payload
},
prioritiesFilterChange: (state, action) => {
state.priorities = action.payload
},
},
})
// Redux Core
import { combineReducers, createStore } from 'redux'
import filtersReducer from '../components/Filters/FiltersSlice'
import todoListReducer from '../components/TodoList/TodosSlice'
import { composeWithDevTools } from 'redux-devtools-extension'
const composedEnhancers = composeWithDevTools()
const rootReducer = combineReducers({
filters: filtersReducer,
todoList: todoListReducer,
})
const store = createStore(rootReducer, composedEnhancers)
export default store
// Redux Toolkit
import { configureStore } from '@reduxjs/toolkit'
import filtersSlice from '../components/Filters/filtersSlice'
import todosSlice from '../components/TodoList/todosSlice'
const store = configureStore({
reducer: {
filters: filtersSlice.reducer,
todoList: todosSlice.reducer,
},
})
export default store
// Redux Core
import { searchFilterChange } from '../../redux/actions'
import { useDispatch } from 'react-redux'
// ...
const dispatch = useDispatch()
const handleSearchTextChange = (e) => {
setSearchText(e.target.value)
dispatch(searchFilterChange(e.target.value))
}
// Redux Toolkit
import filtersSlice from './filtersSlice'
import { useDispatch } from 'react-redux'
// ...
const dispatch = useDispatch()
const handleSearchTextChange = (e) => {
setSearchText(e.target.value)
dispatch(filtersSlice.actions.searchFilterChange(e.target.value))
}
// Redux Core
import { createSelector } from 'reselect'
export const searchTextSelector = (state) => state.filters.search
export const filterStatusSelector = (state) => state.filters.status
export const filterPrioritiesSelector = (state) => state.filters.priorities
export const todoListSelector = (state) => state.todoList
// Redux Toolkit
import { createSelector } from '@reduxjs/toolkit'
export const searchTextSelector = (state) => state.filters.search
export const filterStatusSelector = (state) => state.filters.status
export const filterPrioritiesSelector = (state) => state.filters.priorities
export const todoListSelector = (state) => state.todoList
export const todosRemainingSelector = createSelector(
todoListSelector,
filterStatusSelector,
searchTextSelector,
filterPrioritiesSelector,
(todoList, status, searchText, priorities) => {
return todoList.filter((todo) => {
if (status === 'All') {
return priorities.length
? todo.name.includes(searchText) && priorities.includes(todo.priority)
: todo.name.includes(searchText)
}
return (
todo.name.includes(searchText) &&
(status === 'Completed' ? todo.completed : !todo.completed) &&
(priorities.length ? priorities.includes(todo.priority) : true)
)
})
}
)
const todoList = useSelector(todosRemainingSelector)