Redux (typescript) - Quản lý state trong React
Mặc dù trên mạng đã có rất nhiều bài về redux nhưng blog mình thì chưa 😻
Tại sao lại có Redux
Do yêu cầu cho các ứng dụng single-page sử dụng Javascript ngày càng trở lên phức tạp thì code của chúng ta phải quản lý nhiều state hơn. State có thể bao gồm là data trả về từ phía Server và được cached lại hay như dữ liệu được tạo ra và thao tác ở phía client mà chưa được đẩy lên phía server. UI state cũng càng ngày càng trở lên phức tạp.
Redux là gì?
Bản chất làm việc với React là việc truyền dữ liệu giữa các component và thay đổi state để rerender lại giao diện component. Nếu dự án chúng ta lớn, việc truyền props từ Component cha sang nhiều component con khác sẽ rất khó để theo dõi code và bảo trì code. Redux là thư viện cung cấp cho ta một store trung tâm, lưu trữ tất cả các state, từ component muốn thay đổi state chỉ cần truy cập tới store để thay đổi.
Cấu trúc của Redux
Redux bao gồm 3 thành phần chính
Action
Là nơi mang các thông tin dùng để gửi từ ứng dụng đến Store. Các thông tin này là 1 object gồm type mô tả những gì đã xảy ra và dữ liệu được gửi lên. Nói dễ hiểu, từ 1 component, ta muốn thay đổi state trên store, ta phải gửi action , là một object để miêu tả muốn làm gì.
Reducer
Nơi tiếp nhận action và thay đổi state, làm re-render lại giao diện.Gồm 2 loại:
Root Reducer
: là Boss, quản lý tất cả reducer conChild Reducer
: như đã biết về state, state là một object có nhiều thuộc tính, mỗi child reducer chịu trách nhiệm thay đổi 1 thuộc tính trong state.
Store: Nơi lưu trữ và quản lý state (Chính là Big Boss)
Các phương thức trong store
store.getState()
: Lấy state được lưu trong storestore.dispatch(action)
: Gửi một action gồm một hành động (type) và dữ liệu (payload) lên reducer.store.subcribe()
Nguyên lý hoạt động của redux
Redux hoạt động là khá đơn giản. Nó có 1 store lưu trữ toàn bộ state của app. Mỗi component có thể access trực tiếp đến state được lưu trữ thay vì phải send drop down props từ component này đến component khác.
- Component gửi 1 event tới Action.
- Action tiếp nhận event và dữ liệu đó và sẽ chuyển tới Reducer.
- Reducer sẽ tiếp nhận và xử lý action đó
- Nếu state được lưu trong Reducer bị thay đổi. Store sẽ gửi state mới xuống lại Component, làm Component đó bị re-render lại.
Học redux thông qua ví dụ
Mình sẽ chuẩn bị cho các bạn hai ví dụ
Một ví dụ viết về cách dùng redux trong class component và một ví dụ khác trong function component. Tuy nhiên, mình sẽ hướng dẫn các bạn cách viết function component, còn về class bạn có thể xem link git mình cung cấp ở dưới
B1. Thiết lập reducer
Đầu tiên trong reducers/type.ts
ta định nghĩa 2 action: xoá và thêm cho component person
/**
* Định nghĩa một enum là tên của ```action```
*
* Miêu tả hành động mà ```action``` này đảm nhiệm
*/
export enum EReduxAddPeopleActionTypes {
ADD_PERSON = "ADD_PERSON",
}
/**
* Type định nghĩa cho ```action```
*
* Thông thường ```action``` gồm hai phần là
*
* - ```type``` sẽ miêu tả ```action``` đấy sẽ làm gì
* - ```payload``` dữ liệu mới được gửi lên để thay đổi state
*/
export interface IReduxAddPeopleAction {
type: EReduxAddPeopleActionTypes;
payload: string;
}
/**
* Định nghĩa một enum là tên của ```action```
*
* Miêu tả hành động mà ```action``` này đảm nhiệm
*/
export enum EReduxRemovePeopleActionTypes {
REMOVE_PERSON = "REMOVE_PERSON",
}
/**
* Type định nghĩa cho ```action```
*
* Thông thường ```action``` gồm hai phần là
*
* - ```type``` sẽ miêu tả ```action``` đấy sẽ làm gì
* - ```payload``` dữ liệu mới được gửi lên để thay đổi state
*/
export interface IReduxRemovePeopleAction {
type: EReduxRemovePeopleActionTypes;
payload: number;
}
/**
* Root Action chứa hai action xoa và thêm
*/
export type TPersonActions = IReduxAddPeopleAction | IReduxRemovePeopleAction;
Kế đó tạo child reducer trong reducers/person.ts
import { TPersonState, EReduxAddPeopleActionTypes, EReduxRemovePeopleActionTypes, TPersonActions } from './type';
function neverReached(never: never) {
console.log(never)
console.warn('this action cant be handle by person reducer ')
}
// action sẽ được dispatch bởi một Action Creator (như là event onClick của một button)
export function peopleReducer(state: TPersonState[] = [], action: TPersonActions) {
switch (action.type) {
case EReduxAddPeopleActionTypes.ADD_PERSON:
// state trong react sẽ là immutable nên cần trả lại một giá trị mới
return state.concat({ id: state.length + 1, name: action.payload });
case EReduxRemovePeopleActionTypes.REMOVE_PERSON:
return state.filter(person => person.id !== action.payload);
default:
// bạn có thẻ return lại state
// Hoặc
neverReached(action);
}
return state;
}
Tạo Root Reducer trong reducers/index.ts
import { combineReducers } from 'redux'
import { peopleReducer } from './person';
export const rootReducer = combineReducers({
peopleReducer,
// trong trường hợp có nhiều reducer thì bạn cho vào đây
})
export type TReducers = ReturnType<typeof rootReducer>
B2. Thiết lập các action
Tạo các action trong actions/person.ts
type
sẽ mô tả cho chúng ta biết action đấy để làm gìpayload
là thông tin bổ sung (đính kèm) được gửi kèm theo action
import { EReduxAddPeopleActionTypes, EReduxRemovePeopleActionTypes, IReduxAddPeopleAction, IReduxRemovePeopleAction } from '../reducers/person';
/**
* Action thêm một people
* @param personName tên person nhập ô input
*/
export function addPerson(personName: string): IReduxAddPeopleAction {
return {
type: EReduxAddPeopleActionTypes.ADD_PERSON,
payload: personName
} as const;
}
/**
* Action xoá một person
* @param id id của person bị xoá
*/
export function removePerson(id: number): IReduxRemovePeopleAction {
return {
type: EReduxRemovePeopleActionTypes.REMOVE_PERSON,
payload: id
} as const;
}
B3. Thiết lập store
import * as React from "react";
import { Provider } from "react-redux";
import { createStore } from "redux";
import { rootReducer } from "./reducers";
import "./App.css";
import { Person } from "./components"; // Component này sẽ viết sau nhé
/**
* Tạo store
* @param rootReducer tat ca reducer de tao state moi
*/
const store = createStore(rootReducer);
function App() {
return (
<Provider store={store}> // Provider sẽ cung cấp redux store xuyên suốt app của chúng ta
<h1>Hello World</h1>
<div className="App">
<div>
<p>A simple add/remove person</p>
<Person />
</div>
</div>
</Provider>
);
}
export default App;
B4. Viết component Person và dispatch action, lấy state từ store
Trước khi viết component Person ta định nghĩa type cho state của nó
/**
* Định nghĩa type cho state person component
*/
export type TPersonState = {
id: number;
name: string;
};
Nào bắt tay vào viết thôi
import * as React from "react";
import { useSelector, useDispatch } from "react-redux";
import { TReducers } from '../reducers';
import { addPerson, removePerson } from '../actions';
export const Person = () => {
const [newPerson, setNewPerson] = React.useState("");
const updateNewPerson = () => (e: React.ChangeEvent<HTMLInputElement>) =>
setNewPerson(e.currentTarget.value);
/** Function component dùng ```useSelector``` để lấy state từ store */
const people: TPersonState[] = useSelector((state: TReducers) => state.peopleReducer);
/** Function component dùng ```useDispatch``` hook để dispatch action */
const dispatch = useDispatch();
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
dispatch(addPerson(newPerson)); // dispatch action thêm person
setNewPerson("");
};
const dispatchRemovePerson = (id: number) => () => {
dispatch(removePerson(id)); // dispatch action xoá person
};
return (
<div>
<ul>
{people.map(person => (
<li key={person.id}>
<span>{person.name}</span>
<button onClick={dispatchRemovePerson(person.id)}>Remove</button>
</li>
))}
</ul>
<form onSubmit={handleSubmit}>
<input
placeholder="Enter name"
value={newPerson}
onChange={updateNewPerson()}
/>
<button>Add</button>
</form>
</div>
);
};
Github
Các bạn download src code ở đây
Comments