Giới thiệu
Hello Anh Em. Trong quá trình làm việc, mình đã gặp phải một vấn đề khi cần phân tách state trong ứng dụng Angular của mình. Sau khi gặp vấn đề này, mình đã dành thời gian nghiên cứu kỹ và tìm hiểu về cách giải quyết nó. Hôm nay, mình rất vui được chia sẻ lại kiến thức và kinh nghiệm của mình thông qua việc tạo ra tài liệu này.
Trong các ứng dụng web lớn, việc quản lý state một cách hiệu quả là rất quan trọng để nâng cao hiệu năng và trải nghiệm người dùng. Đặc biệt, khi một trang web chứa nhiều tab với nội dung và dữ liệu khác nhau, việc tách biệt state cho từng tab có thể giúp cải thiện đáng kể hiệu suất ứng dụng. Tuy nhiên, trong một số trường hợp, quản lý state chung có thể dẫn đến tình trạng trang web trở nên chậm chạp và không hiệu quả.
Bài viết này sẽ hướng dẫn cách chuyển từ việc quản lý state chung sang quản lý state riêng biệt cho từng tab, một cách tổ chức và hiệu quả hơn. Chúng ta sẽ sử dụng một ví dụ cụ thể từ một ứng dụng quản lý sách với hai tab: "Tất Cả Sách" và "Yêu Thích". Qua đó, bạn sẽ nhận được cái nhìn rõ ràng và chi tiết về cách cải thiện hiệu suất của ứng dụng bằng cách tối ưu hóa quản lý state.
Trước khi bắt đầu, hãy đảm bảo rằng bạn đã có kiến thức cơ bản về Angular và ngRx để có thể hiểu rõ nội dung của bài viết. Hãy chuẩn bị sẵn tinh thần tập trung và kiên nhẫn, vì chúng ta sẽ đi sâu vào các khái niệm kỹ thuật và cách triển khai chúng.
Phần I: Phân Tích Vấn Đề
Tình trạng hiện tại
Ứng dụng quản lý sách của chúng ta hiện đang quản lý state thông qua một cấu trúc chung dưới dạng AppState, bao gồm hai phần chính: allBooks và favoriteBooks. Mỗi phần này chứa một danh sách các sách nhưng không hỗ trợ phân trang. Việc truy vấn dữ liệu từ server cũng được thực hiện thông qua một API duy nhất, trả về tất cả dữ liệu cùng một lúc.
interface AppState {
pageName: string;
pageDescription: string;
booksPage: {
allBooks: {
list: Book[];
};
favoriteBooks: {
list: Book[];
};
};
}
Vấn đề
- Quản lý State Chưa Tối Ưu: State hiện tại không hỗ trợ phân trang, dẫn đến việc tải và hiển thị một lượng lớn dữ liệu cùng một lúc, gây chậm trễ và ảnh hưởng đến hiệu năng.
- Sử Dụng Một API Duy Nhất: Việc dùng chung một API để truy vấn tất cả dữ liệu là không hiệu quả, nhất là khi ứng dụng cần mở rộng và tối ưu hóa. ### Yêu cầu mới
Để cải thiện hiệu năng và trải nghiệm người dùng, chúng ta cần phải:
- Thêm phân trang cho danh sách sách và sách yêu thích.
- Phân chia và quản lý state riêng biệt cho mỗi tab allBooks và favoriteBooks.
- Sử dụng các API riêng biệt cho mỗi tab để truy vấn dữ liệu, cho phép tải dữ liệu theo từng phần.
Phần II: Giải Pháp
Cập nhật Cấu Trúc State
Cập nhật AppState để hỗ trợ phân trang cho mỗi tab:
interface AppState {
pageName: string;
pageDescription: string;
booksPage: {
allBooks: {
list: Book[];
pagination: Pagination;
};
favoriteBooks: {
list: Book[];
pagination: Pagination;
};
};
}
Phân Chia Backend Services
Chia Backend Service thành hai dịch vụ riêng biệt cho AllBooks và FavoriteBooks, để gọi API lấy dữ liệu theo phần và hỗ trợ phân trang.
Định Nghĩa Actions Mới
Tạo các actions mới để cập nhật danh sách sách và thông tin phân trang cho mỗi tab, cùng với các actions xử lý lỗi tương ứng.
Cập Nhật Reducers
Xây dựng các reducers tương ứng để xử lý các actions mới, cập nhật state cho mỗi phần một cách độc lập.
Cập nhật Effects
Effects được sử dụng để xử lý logic bất đồng bộ, gọi Backend Service và dispatch các actions tương ứng dựa trên kết quả, giúp quản lý dữ liệu một cách hiệu quả.
Cập nhật Selectors
Selectors được cập nhật để dễ dàng truy xuất danh sách sách, thông tin phân trang, và lỗi từ state, giúp cải thiện hiệu suất và dễ dàng quản lý dữ liệu trong các component.
Cập nhật lớp Component, Page services
Chia nhỏ component và page service giúp chúng ta quản lý dữ liệu và logic của từng phần của ứng dụng một cách rõ ràng hơn. Đồng thời, nó cũng giúp tăng cường khả năng mở rộng và bảo trì của ứng dụng.
Phần III: Triển Khai
1. Phân Chia Backend Services
Việc đầu tiên cần làm là chia nhỏ backend service, bằng cách cập nhật thêm các phương thức mới. Mỗi phương thức sẽ chịu trách nhiệm gọi các API tương ứng để lấy dữ liệu cho mỗi phần của ứng dụng. Đồng thời, các phương thức này cũng sẽ hỗ trợ phân trang dữ liệu, giúp chúng ta quản lý dữ liệu một cách linh hoạt và hiệu quả hơn.
Before:
Một single service BooksService xử lý tất cả các loại truy vấn API liên quan đến sách, không phân biệt giữa danh sách sách tổng và sách yêu thích.
@Injectable({ providedIn: 'root' })
export class BooksService {
constructor(private http: HttpClient) {}
getBooks(): Observable<{ allBooks: Book[], favoriteBooks: Book[] }> {
// Gọi một API duy nhất để lấy tất cả sách và sách yêu thích
return this.http.get<{ allBooks: Book[], favoriteBooks: Book[] }>('api/books');
}
}
After:
Tách BooksService thành hai service riêng biệt: AllBooksService và FavoriteBooksService, mỗi service này sẽ xử lý giao tiếp API riêng biệt cho từng tab, hỗ trợ phân trang.
@Injectable({ providedIn: 'root' })
export class BooksService {
constructor(private http: HttpClient) {}
// Phương thức ban đầu không thay đổi
getBooks(): Observable<{ allBooks: Book[], favoriteBooks: Book[] }> {
return this.http.get<{ allBooks: Book[], favoriteBooks: Book[] }>('api/books');
}
// Thêm phương thức mới để lấy tất cả sách với phân trang
getAllBooks(pagination: Pagination): Observable<{ list: Book[], pagination: Pagination }> {
const params = new HttpParams()
.set('page', pagination.currentPage.toString())
.set('limit', pagination.itemsPerPage.toString());
return this.http.get<{ list: Book[], pagination: Pagination }>('api/books/all', { params });
}
// Thêm phương thức mới để lấy sách yêu thích với phân trang
getFavoriteBooks(pagination: Pagination): Observable<{ list: Book[], pagination: Pagination }> {
const params = new HttpParams()
.set('page', pagination.currentPage.toString())
.set('limit', pagination.itemsPerPage.toString());
return this.http.get<{ list: Book[], pagination: Pagination }>('api/books/favorites', { params });
}
}
Việc này giúp BooksService trở nên linh hoạt hơn trong việc xử lý các yêu cầu khác nhau từ phía frontend, từ việc lấy toàn bộ danh sách sách mà không cần phân trang đến việc lấy dữ liệu theo từng phần với phân trang, tất cả trong một service duy nhất.
2. Cập Nhật Actions
Tiếp theo, chúng ta sẽ điều chỉnh và mở rộng các actions có sẵn để phản ánh việc phân chia backend services thành các dịch vụ riêng biệt cho AllBooks và FavoriteBooks. Điều này đòi hỏi chúng ta phải tạo ra các actions mới để tải dữ liệu cho mỗi phần của ứng dụng một cách độc lập, cũng như thêm các actions để xử lý việc cập nhật phân trang dữ liệu. Qua đó, chúng ta sẽ có khả năng quản lý và điều chỉnh dữ liệu của từng phần trong ứng dụng một cách linh hoạt và hiệu quả hơn.
Before:
Sử dụng một loạt actions chung như LoadBooks để truy vấn tất cả sách mà không phân biệt giữa danh sách tổng và yêu thích.
export const loadBooks = createAction('[Books Page] Load Books');
export const loadBooksSuccess = createAction('[Books Page] Load Success', props<{ allBooks: Book[], favoriteBooks: Book[] }>());
export const loadBooksFailure = createAction('[Books Page] Load Failure', props<{ error: any }>());
After:
Định nghĩa actions riêng biệt cho mỗi tab như LoadAllBooks, LoadFavoriteBooks, cùng với các actions thành công và thất bại tương ứng, hỗ trợ phân trang.
// Tải dữ liệu mặc định khi vào trang
export const loadBooks = createAction('[Books Page] Load Books');
export const loadBooksSuccess = createAction('[Books Page] Load Success', props<{ allBooks: Book[], favoriteBooks: Book[] }>());
export const loadBooksFailure = createAction('[Books Page] Load Failure', props<{ error: any }>());
// Tải sách cho từng tab với phân trang
export const loadAllBooks = createAction('[All Books] Load', props<{ pagination: Pagination }>());
export const loadAllBooksSuccess = createAction('[All Books] Load Success', props<{ list: Book[], pagination: Pagination }>());
export const loadAllBooksFailure = createAction('[All Books] Load Failure', props<{ error: any }>());
export const loadFavoriteBooks = createAction('[Favorite Books] Load', props<{ pagination: Pagination }>());
export const loadFavoriteBooksSuccess = createAction('[Favorite Books] Load Success', props<{ list: Book[], pagination: Pagination }>());
export const loadFavoriteBooksFailure = createAction('[Favorite Books] Load Failure', props<{ error: any }>());
// Cập nhật phân trang mà không tải lại danh sách sách
export const updateAllBooksPagination = createAction('[All Books] Update Pagination', props<{ pagination: Pagination }>());
export const updateFavoriteBooksPagination = createAction('[Favorite Books] Update Pagination', props<{ pagination: Pagination }>());
Với cách tổ chức này:
- Khi người dùng lần đầu tiên vào trang, bạn có thể dispatch loadBooks để tải dữ liệu mặc định cho cả hai tab mà không cần phân trang.
- Khi người dùng chuyển đổi giữa các tab và/hoặc tương tác với các tính năng phân trang (chẳng hạn như chuyển trang hoặc thay đổi số mục trên mỗi trang), bạn có thể dispatch các actions riêng biệt như loadAllBooks, loadFavoriteBooks, updateAllBooksPagination, hoặc updateFavoriteBooksPagination tùy thuộc vào tình huống cụ thể.
3. Cập Nhật Reducers
Trong phần cập nhật Reducers, chúng ta sẽ điều chỉnh cấu trúc của reducers để phản ánh việc phân chia dữ liệu thành các phần riêng biệt trong ứng dụng. Điều này bao gồm thêm luồng xử lý mới trong reducer hiện tại để xử lý dữ liệu của từng phần. Chúng ta sẽ xử lý việc cập nhật state khi nhận được dữ liệu từ các actions mới được tạo ra trong phần trước.
Before:
Một reducer booksPageReducer xử lý tất cả các actions liên quan đến sách.
const initialState: BooksPageState = {
pageName: string;
pageDescription: string;
allBooks: {
list: []
},
favoriteBooks: {
list: []
}
};
const booksPageReducer = createReducer(
initialState,
on(loadBooksSuccess, (state, { allBooks, favoriteBooks }) => ({
...state,
allBooks: { ...state.allBooks, list: allBooks },
favoriteBooks: { ...state.favoriteBooks, list: favoriteBooks }
})),
on(loadBooksFailure, (state, { error }) => ({
...state,
// Xử lý lỗi tại đây
}))
);
After:
Để hỗ trợ việc tải dữ liệu mặc định khi vào trang và cập nhật dữ liệu riêng biệt với phân trang cho mỗi tab, chúng ta cần cập nhật reducer để xử lý các actions tương ứng.
// Khởi tạo state ban đầu
const initialState: BooksPageState = {
pageName: string;
pageDescription: string;
allBooks: {
list: [],
pagination: {
currentPage: 0,
totalPages: 0,
totalItems: 0,
itemsPerPage: 0,
}
},
favoriteBooks: {
list: [],
pagination: {
currentPage: 0,
totalPages: 0,
totalItems: 0,
itemsPerPage: 0,
}
},
loading: false,
error: null,
};
// Reducer cập nhật
const booksPageReducer = createReducer(
initialState,
// Xử lý tải dữ liệu mặc định khi vào trang
on(loadBooks, state => ({ ...state, loading: true })),
on(loadBooksSuccess, (state, { allBooks, favoriteBooks }) => ({
...state,
allBooks: { ...state.allBooks, list: allBooks },
favoriteBooks: { ...state.favoriteBooks, list: favoriteBooks },
loading: false
})),
on(loadBooksFailure, (state, { error }) => ({ ...state, error, loading: false })),
// Xử lý tải sách cho tab AllBooks với phân trang
on(loadAllBooks, state => ({ ...state, loading: true })),
on(loadAllBooksSuccess, (state, { list, pagination }) => ({
...state,
allBooks: { list, pagination },
loading: false
})),
on(loadAllBooksFailure, (state, { error }) => ({ ...state, error, loading: false })),
// Xử lý tải sách cho tab FavoriteBooks với phân trang
on(loadFavoriteBooks, state => ({ ...state, loading: true })),
on(loadFavoriteBooksSuccess, (state, { list, pagination }) => ({
...state,
favoriteBooks: { list, pagination },
loading: false
})),
on(loadFavoriteBooksFailure, (state, { error }) => ({ ...state, error, loading: false })),
// Xử lý cập nhật phân trang cho tab AllBooks
on(updateAllBooksPagination, (state, { pagination }) => ({
...state,
allBooks: { ...state.allBooks, pagination }
})),
// Xử lý cập nhật phân trang cho tab FavoriteBooks
on(updateFavoriteBooksPagination, (state, { pagination }) => ({
...state,
favoriteBooks: { ...state.favoriteBooks, pagination }
}))
);
// Đừng quên export reducer
export default booksPageReducer;
Trong cấu trúc này:
- Các actions loadBooks, loadBooksSuccess, và loadBooksFailure giúp xử lý việc tải dữ liệu mặc định khi người dùng lần đầu tiên vào trang.
- Các actions như loadAllBooks, loadAllBooksSuccess, loadAllBooksFailure, loadFavoriteBooks, loadFavoriteBooksSuccess, và loadFavoriteBooksFailure giúp xử lý việc tải dữ liệu với phân trang cho từng tab.
- Các actions updateAllBooksPagination và updateFavoriteBooksPagination cho phép cập nhật thông tin phân trang cho từng tab mà không cần tải lại danh sách sách.
- Thuộc tính loading trong state được sử dụng để theo dõi trạng thái loading của các yêu cầu API, giúp ứng dụng có thể hiển thị trạng thái loading tương ứng.
4. Cập nhật Effects
Tiếp theo, chúng ta sẽ điều chỉnh các effects để xử lý các tác động từ các actions mới và tương tác với backend services. Điều này bao gồm việc tạo ra các effects mới để xử lý tải dữ liệu cho mỗi phần của ứng dụng độc lập nhau, thay vì sử dụng một effect chung như trước đây. Chúng ta cũng sẽ xử lý các tác động khi cần cập nhật phân trang dữ liệu cho từng phần của ứng dụng.
Before:
Một số effects xử lý gọi API chung cho sách mà không tách biệt giữa các tab.
@Injectable()
export class BooksEffects {
loadBooks$ = createEffect(() => this.actions$.pipe(
ofType(loadBooks),
mergeMap(() => this.booksService.getBooks().pipe(
map(({ allBooks, favoriteBooks }) => loadBooksSuccess({ allBooks, favoriteBooks })),
catchError(error => of(loadBooksFailure({ error })))
))
));
constructor(
private actions$: Actions,
private booksService: BooksService
) {}
}
After:
Để cập nhật BooksEffects và hỗ trợ việc tải sách với phân trang cho từng tab, bạn có thể thêm các effects mới sử dụng các action và service mà chúng ta đã định nghĩa.
@Injectable()
export class BooksEffects {
// Effect ban đầu để tải toàn bộ sách khi vào trang
loadBooks$ = createEffect(() => this.actions$.pipe(
ofType(loadBooks),
mergeMap(() => this.booksService.getBooks().pipe(
map(({ allBooks, favoriteBooks }) => loadBooksSuccess({ allBooks, favoriteBooks })),
catchError(error => of(loadBooksFailure({ error })))
))
));
// Thêm effect để tải sách 'All Books' với phân trang
loadAllBooks$ = createEffect(() => this.actions$.pipe(
ofType(loadAllBooks),
mergeMap(({ pagination }) => this.booksService.getAllBooks(pagination).pipe(
map(({ list, pagination }) => loadAllBooksSuccess({ list, pagination })),
catchError(error => of(loadAllBooksFailure({ error })))
))
));
// Thêm effect để tải sách 'Favorite Books' với phân trang
loadFavoriteBooks$ = createEffect(() => this.actions$.pipe(
ofType(loadFavoriteBooks),
mergeMap(({ pagination }) => this.booksService.getFavoriteBooks(pagination).pipe(
map(({ list, pagination }) => loadFavoriteBooksSuccess({ list, pagination })),
catchError(error => of(loadFavoriteBooksFailure({ error })))
))
));
constructor(
private actions$: Actions,
private booksService: BooksService
) {}
}
Trong đoạn code trên:
- loadBooks$ là effect ban đầu để tải toàn bộ sách khi người dùng vào trang, không thay đổi so với trước.
- loadAllBooks$ là effect mới để tải dữ liệu cho tab All Books với phân trang. Effect này lắng nghe action loadAllBooks, gọi method getAllBooks() từ BooksService với thông tin phân trang, và dispatch action loadAllBooksSuccess hoặc loadAllBooksFailure tùy theo kết quả trả về từ API.
- loadFavoriteBooks$ là effect mới để tải dữ liệu cho tab Favorite Books với phân trang. Cách hoạt động tương tự như loadAllBooks$ nhưng dành riêng cho sách yêu thích.
5. Cập nhật Selectors
Trong phần cập nhật Selectors, chúng ta sẽ điều chỉnh các selectors để phản ánh cấu trúc mới của state và các thay đổi trong dữ liệu. Điều này bao gồm việc tạo ra các selectors mới để truy cập dữ liệu cho từng phần của ứng dụng, như AllBooks và FavoriteBooks, đồng thời xử lý các truy vấn phân trang dữ liệu một cách hiệu quả.
Before:
Sử dụng các selectors chung để lấy danh sách sách từ state.
export const selectBooksPageState = (state: AppState) => state.booksPage;
After:
Đầu tiên, hãy đảm bảo rằng chúng ta có một selector cơ bản để truy xuất booksPage từ AppState
sau đó tạo các selector mới cho allBooks và favoriteBooks, bao gồm cả danh sách sách và thông tin phân trang:
export const selectBooksPageState = (state: AppState) => state.booksPage;
// Selector cho danh sách sách 'All Books'
export const selectAllBooksList = createSelector(
selectBooksPageState,
(booksPage) => booksPage.allBooks.list
);
export const selectAllBooksPagination = createSelector(
selectBooksPageState,
(booksPage) => booksPage.allBooks.pagination
);
// Selector cho danh sách sách 'Favorite Books'
export const selectFavoriteBooksList = createSelector(
selectBooksPageState,
(booksPage) => booksPage.favoriteBooks.list
);
export const selectFavoriteBooksPagination = createSelector(
selectBooksPageState,
(booksPage) => booksPage.favoriteBooks.pagination
);
Trong đoạn code trên:
- selectAllBooksList và selectFavoriteBooksList là các selector giúp truy xuất danh sách sách từ state cho từng tab.
- selectAllBooksPagination và selectFavoriteBooksPagination là các selector giúp truy xuất thông tin phân trang từ state cho từng tab.
- Nhờ có các selector này, components của bạn có thể dễ dàng subscribe tới dữ liệu cụ thể mà chúng cần từ state mà không phải lo lắng về việc xử lý logic truy xuất phức tạp trực tiếp trong component. Điều này giúp giữ cho code của bạn được tổ chức tốt hơn, dễ bảo trì hơn và tái sử dụng được nhiều hơn.
6. Cập nhật Component
Tiếp theo là cập nhật Component, chúng ta sẽ điều chỉnh các thành phần giao diện người dùng để phản ánh cấu trúc mới của dữ liệu và state. Điều này bao gồm việc cập nhật các component để hiển thị dữ liệu từ các phần riêng biệt của ứng dụng, chẳng hạn như AllBooks và FavoriteBooks. Đồng thời, chúng ta cũng cần điều chỉnh logic trong các component để đảm bảo rằng chúng hiển thị dữ liệu đúng cách và phản ánh các thay đổi trong state.
Before:
Ban đầu, BookPageComponent có thể đã được thiết kế để tải toàn bộ dữ liệu sách một lần mà không cần phân trang và không xử lý dữ liệu chung của trang như tên trang và mô tả.
@Component({
// Metadata
})
export class BookPageComponent implements OnInit {
books$: Observable<Book[]>; // Dữ liệu sách từ state
constructor(private store: Store<AppState>) {}
ngOnInit(): void {
// Dispatch action để tải toàn bộ sách khi vào trang
this.store.dispatch(loadBooks());
this.books$ = this.store.pipe(select(selectBooks));
}
}
After:
Trong trường hợp này, khi truy cập vào Page, ứng dụng sẽ tải dữ liệu chung cho trang như tên trang, mô tả trang, và cả danh sách sách cho mỗi tab mà không phải đợi phân trang. Và sau đó, khi người dùng tương tác với phân trang, bạn chỉ cập nhật dữ liệu sách riêng cho mỗi tab. Dưới đây là cách bạn có thể cập nhật BookPageComponent:
@Component({
// Metadata
})
export class BookPageComponent implements OnInit {
pageData$: Observable<PageData>; // Observable cho dữ liệu chung của trang
allBooks$: Observable<Book[]>;
favoriteBooks$: Observable<Book[]>;
constructor(private store: Store<AppState>) {}
ngOnInit(): void {
// Tải dữ liệu chung cho trang bao gồm tải sách ban đầu cho mỗi tab mà không phân trang
this.store.dispatch(loadPageData());
// Lấy dữ liệu chung của trang và sách từ state
this.pageData$ = this.store.pipe(select(selectPageData));
this.allBooks$ = this.store.pipe(select(selectAllBooksList));
this.favoriteBooks$ = this.store.pipe(select(selectFavoriteBooksList));
}
loadBooksForTab(tab: 'all' | 'favorite', pagination: Pagination): void {
if (tab === 'all') {
this.store.dispatch(loadAllBooks({ pagination }));
} else if (tab === 'favorite') {
this.store.dispatch(loadFavoriteBooks({ pagination }));
}
}
// Xử lý khi người dùng tương tác với phân trang
onPageChange(tab: 'all' | 'favorite', page: number): void {
this.loadBooksForTab(tab, { currentPage: page, itemsPerPage: 10 });
}
}
Các Điểm Chính:
- Dữ liệu chung của trang: pageData$ là một Observable được sử dụng để hiển thị dữ liệu chung của trang như tên trang và mô tả. Action loadPageData sẽ được dispatch trong ngOnInit để tải dữ liệu này.
- Tải sách cho lần đầu tiên: loadBooks action được dispatch ngay trong ngOnInit để tải danh sách sách đầu tiên cho cả hai tab, đảm bảo dữ liệu có sẵn ngay khi trang được tải.
- Xử lý phân trang: loadBooksForTab là một phương thức giúp tải dữ liệu sách cho tab tương ứng khi người dùng tương tác với phân trang, sử dụng các action loadAllBooks và loadFavoriteBooks với thông tin phân trang cụ thể.
- Phản hồi UI: Khi người dùng tương tác với các thành phần phân trang, phương thức onPageChange sẽ được gọi, và nó sẽ lại gọi loadBooksForTab với thông tin tab và trang mới. Như vậy, bạn sẽ có một cách tổ chức linh hoạt, cho phép tải dữ liệu chung của trang và dữ liệu sách ngay khi truy cập trang, đồng thời hỗ trợ cập nhật dữ liệu sách khi tương tác với phân trang mà không làm ảnh hưởng đến dữ liệu chung của trang.
Kết luận
Vậy là chúng ta đã đi qua một hành trình cập nhật và tối ưu hóa BookPageComponent trong ứng dụng quản lý sách, từ một cấu trúc ban đầu đơn giản, không hỗ trợ phân trang hoặc dữ liệu chung của trang, đến một cấu trúc linh hoạt hơn, hỗ trợ phân trang và quản lý dữ liệu chung.
Tóm Lược Thay Đổi:
- Cấu Trúc State và Service: Chúng ta đã cập nhật cấu trúc state để hỗ trợ phân trang và thêm phương thức vào service để hỗ trợ tải dữ liệu với phân trang.
- Actions và Reducers: Đã thêm các actions mới và cập nhật reducers để xử lý việc tải và cập nhật sách với phân trang, cũng như hỗ trợ tải dữ liệu chung của trang.
- Selectors: Cập nhật và tạo mới các selectors để truy xuất dữ liệu sách và dữ liệu chung của trang từ state một cách hiệu quả.
- Component: Cập nhật logic của component để hỗ trợ tải dữ liệu ban đầu mà không cần phân trang và tải dữ liệu theo từng tab với phân trang khi tương tác với UI, đồng thời quản lý dữ liệu chung của trang. Quá trình cập nhật BookPageComponent chứng minh rằng, thông qua việc cải tiến cấu trúc và logic ứng dụng, chúng ta có thể đạt được sự cân bằng giữa hiệu năng và trải nghiệm người dùng. Điều này không chỉ giúp ứng dụng của anh em mạnh mẽ hơn và phản hồi nhanh hơn mà còn tạo ra một cơ sở vững chắc cho sự phát triển và mở rộng trong tương lai. Cuối cùng, mình cám ơn và hy vọng rằng bài viết này đã cung cấp cho anh em cái nhìn rõ ràng và các bước cụ thể để cải thiện hiệu suất và quản lý state trong dự án.
Top comments (0)