Redux là 1 state management phổ biến trong hệ sinh thái React, nếu các bạn code React thì khả năng cao đã từng làm việc với Redux, kinh qua nhiều project hoặc chí ít cũng đã từng biết đến nó rồi. Redux được phát triển từ những năm 2015 và được ưa chuộng phổ biến giúp việc quản lý state trong React. Sau đó như mọi người biết ở version 16.8, React có trình làng React Hooks vào khoảng tháng 2 năm 2019. Bản thân Hooks sinh ra thì không liên quan gì lắm đến Redux, Hook nhằm giải quyết vấn đề tái sử dụng logic code. Tuy nhiên Hooks cũng có những tác động nhất định lên Redux, kết hợp với những vấn đề đã tồn đọng từ lâu trong phiên bản Redux core; chính vì thế team phát triển Redux cũng đã tìm cách khắc phục, cải thiện và đã cho ra mắt Redux Toolkit vào tháng 10 năm 2019.

Trong phần giới thiệu về Redux Toolkit, team Redux cũng đã giải thích chi tiết mục tiêu cho ra đời RTK (Redux Toolkit), bạn có thể đọc ở link sau và mình cũng xin trích ra dưới đây xem như phần định nghĩa cho Redux Toolkit:

https://redux-toolkit.js.org/introduction/getting-started

Redux Toolkit package giúp chuẩn hóa cách viết Redux logic, nó được tạo ra để giúp giải quyết 1 số vấn đề than phiền về Redux như sau:

  • Việc cấu hình 1 Redux store quá phức tạp
  • Cần kết hợp kha khá các packages khác cùng Redux mới có thể sử dụng 1 cách hữu ích trong ứng dụng React
  • Redux yêu cầu quá nhiều boilerplate code (code mẫu, code theo chuẩn)

Hẳn các bạn nếu làm việc nhiều với Redux cũng sẽ gặp phải những vấn đề trên. Để tạo ra được 1 store hoàn chỉnh chúng ta phải trải qua khá nhiều bước với các đoạn code lặp lại mà redux thì chả có cảnh báo cũng như xây dựng quy chuẩn nào rõ ràng cho việc đó. Rồi ai dùng Redux thì cũng add thêm nhiều thư viện middleware, selectors khác như thunk, saga, … thì mới có thể hoàn chỉnh được logic quản lý state trong ứng dụng của bạn được. Nhận ra được vấn đề đó (theo quan điểm cá nhân của mình thì cũng 1 phần từ Hooks như nhắc đến đầu bài, ít nhất về mặt thời gian), team Redux đã giới thiệu RTK giúp chúng ta việc code redux nhanh hơn, gọn hơn và hoàn chỉnh theo 1 quy chuẩn thống nhất hơn.

Lưu ý các bạn 1 chút, nếu bạn đã nắm được Redux cơ bản, hiểu rõ các khái niệm về actions, reducer, store rồi thì việc tiếp cận RTK như là 1 cách tự nhiên, 1 bước cải tiến của bạn. Nhưng nếu bạn chưa biết hay làm qua Redux, mình khuyên nên khoan vội tìm hiểu RTK, hãy quay lại đọc Redux core như 1 kiến thức nền trước.

Trong bài hôm nay mình sẽ chỉ tập trung phân tích và so sánh cách triển khai code của RTK so với Redux trước đây để bạn hiểu hơn lí do nó được tạo ra. Trong cách bài viết theo mình sẽ giới thiệu chi tiết hơn cách cài đặt, triển khai và đi sâu vào các thành phần trong RTK như: createSlice, createAsyncThunk, createSelector, RTK Query, …

Đầu tiên, đây là cách các bạn tạo ra 1 store trong Redux, sẽ rất quen thuộc:

// store.js
import { createStore } from 'redux'

const INCREASEMENT = 'increasement'
const DECREASEMENT = 'decreasement'

// actions
export const increasement = () => {{ type: INCREASEMENT })
export const decreasement = () => ({ type: DECREASEMENT })

const initialState = { count: 0 }

//reducer
function rootReducer(state = initialState, action) {
    switch (action.type) {
        case INCREASEMENT:
            return { count: state.count + 1 };
            break;
        case DECREASEMENT:
            return { count: state.count - 1 };
            break;
        default:
            return state
    }
}

const store = createStore(rootReducer)

export default store

Đây là ví dụ mặc định (template) của redux nên mình xin phép không giải thích code ở đây (nó cũng quá đơn giản mà). Có 1 vài vấn đề ở đây:

  • Hơi dài dòng: việc khai báo type (INCREASEMENT và DECREASEMENT) thực sự thì cũng chỉ để action và reducer dùng thôi
  • action và reducer kết hợp xử lý logic bằng JS nhưng không có quy chuẩn nào được đưa ra: ông action nếu không trả về type thì sao, có cần viết switch case để check action trong reducer hay không
  • createStore ở đây chỉ nhận 1 reducer, tất nhiên Redux cho phép bạn chia nhỏ reducer thành các reducer nhỏ hơn và sử dụng combineReducers để gộp chúng nó lại với nhau. Nhưng thử hỏi có cái application nào mà chỉ viết 1 file reducer thôi không, sao ngay từ đầu không cung cấp sẵn luôn đi, mất công combine làm gì?

OK chúng ta bắt đầu viết qua RTK.

Bước 1 là khai báo store

import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({
  reducer: rootReducer
})

Ở đây configureStore của RTK nhìn thì giống createStore, nhưng mặc định nó đã được thiết lập 1 số middleware sẵn (mình sẽ trình bày chi tiết ở các bài viết sau) cũng như cho phép sử dụng redux devtool để debug và theo dõi quá trình state thay đổi. Tiện sẵn luôn đúng không, thay vì như cũ sẽ phải viết đoạn code đại loại như này:

import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "../reducers/index";
import { forbiddenWordsMiddleware } from "../middleware";
import thunk from "redux-thunk";

const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

const store = createStore(
  rootReducer,
  storeEnhancers(applyMiddleware(forbiddenWordsMiddleware, thunk))
);

export default store;

Bước 2 là tạo action

import { createAction } from '@reduxjs/toolkit';

const increment = createAction('INCREMENT')

console.log(increment.type)
// "INCREMENT"

Thay vì 2 dòng code thì giờ gộp thành 1 dòng và lúc nào cũng trả về field type luôn, đỡ mất công viết lại.

Bước 3 là tạo reducer

import {createReducer} from '@reduxjs/toolkit'

const counter = createReducer({ count: 0 }, {
  [increment]: state => ({ count: state.count + 1 }),
  [decrement]: state => ({ count: state.count - 1 })
}

Ở đây logic chúng ta viết trông nó sẽ trực quan hơn, dạng key – value sẽ đơn giản và ngắn gọn hơn so với cách viết thuần.

Cuối cùng thì tổng hợp lại code, RTK sẽ viết chung lại như sau:

// store.js
import { configureStore, createAction, createReducer } from '@reduxjs/toolkit';

// actions
const increasement = createAction('increasement')
const decreasement = createAction('decreasement')

const initialState = { count: 0 }

function rootReducer = createReducer(initialState, {
    [increasement]: state => ({ count: state.count + 1 })
    [decreasement]: state => ({ count: state.count - 1 })
})

const store = configureStore({
    reducer: rootReducer
})

export default store

Gọn hơn và nhìn có vẻ chuyên nghiệp hơn đúng không các bạn. Tất nhiên là logic vẫn vậy, luồng tư duy vẫn vậy, nhưng các bạn sẽ giảm được sai sót khi gõ code, giảm các đoạn code mẫu lặp lại kha khá, nhìn cũng sướng mắt nữa.

Bài hôm nay đến đây thôi nhé, mình sẽ viết thêm về RTK sau vì còn nhiều cái hay ho, các APIs khác mà RTK cung cấp nữa. Cảm ơn mọi người

From Anyway with Love