Create a example with React Redux Toolkit

Đầu tiên chúng ta có thể cài đặt thông qua các câu lệnh dưới đây

# Redux + Plain JS template
npx create-react-app my-app --template redux

# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript

Bộ công cụ Redux có sẵn dưới dạng gói trên NPM để sử dụng với gói mô-đun hoặc trong ứng dụng Node:

# NPM
npm install @reduxjs/toolkit

# Yarn
yarn add @reduxjs/toolkit

Bộ công cụ Redux bao gồm các API này: configureStore(), createReducer(), createAction(), createSlice(), createAsyncThunk, createEntityAdapter, createSelector , mọi người có thể tim hiều và các hàm này nhé!

+ configureStore() : cấu hình các store reducers trong đây nhé, đồng thời viết các middleware vào đây luôn

export const store = configureStore({
  reducer: {
    counter: counterReducer,
     users : usersReducer, 
     posts : postsReducer,
     ......
  },
   // chổ concat ta có thể đưa vào [] mảng list middleware concat[...]
   middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
});

Bạn có thể tìm thêm tại đây: https://redux-toolkit.js.org/api/configureStore

+ createReducer() : Ta hiểu như thế này, ta cần viết các actions trong reducers, nó sẽ kiểm trả xem dispatch action vào có nằm trong case reducers hay không, nếu nó nằm trong đó sẽ tiến hành update state 

createReducer(0, {
  increment: (state, action: PayloadAction<number>) => state + action.payload,
})

Ví dụ bạn nhìn dưới đây, dưới đây là cách đơn giản ta cấu hình các actions trong reducers 

function todosReducer(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO': {
      return state.concat(action.payload)
    }
    case 'TOGGLE_TODO': {
      const { index } = action.payload
      return state.map((todo, i) => {
        if (i !== index) return todo

        return {
          ...todo,
          completed: !todo.completed,
        }
      })
    }
    case 'REMOVE_TODO': {
      return state.filter((todo, i) => i !== action.payload.index)
    }
    default:
      return state
  }
}

Còn nếu bạn muốn viết theo kểu rút gọn thì ta có thể dùng cách sau:

const todosReducer = createReducer([], (builder) => {
  builder
    .addCase('ADD_TODO', (state, action) => {
      // "mutate" the array by calling push()
      state.push(action.payload)
    })
    .addCase('TOGGLE_TODO', (state, action) => {
      const todo = state[action.payload.index]
      // "mutate" the object by overwriting a field
      todo.completed = !todo.completed
    })
    .addCase('REMOVE_TODO', (state, action) => {
      // Can still return an immutably-updated value if we want to
      return state.filter((todo, i) => i !== action.payload.index)
    })
})

+ createAction() : Cách thông thường để xác định một hành động trong Redux là khai báo riêng một hằng số loại hành động và một hàm tạo hành động để xây dựng các hành động thuộc loại đó.

import { createAction } from '@reduxjs/toolkit'

const increment = createAction<number | undefined>('counter/increment')

let action = increment()
// { type: 'counter/increment' }

action = increment(3)
// returns { type: 'counter/increment', payload: 3 }

console.log(increment.toString())
// 'counter/increment'

console.log(`The action type is: ${increment}`)
// 'The action type is: counter/increment'

::) Các bạn tìm hiểu thêm về các API của Redux thêm nhé

Okay  bây giờ mình sẽ tạo một project bằng template redux nhé :

# Redux + Plain JS template
npx create-react-app my-app --template redux

Sau khi thực hiện câu lệnh trên ta sẽ được project như thế này

Giờ mình đi tìm hiểu sơ qua về các file trong thư mục project trên thử nhé.

+ app/store.js: 

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

Ta thấy nó có gọi thư viện @reduxjs/toolkit , đồng thời gọi file counterSlice trong đường dẫn ../features/counters , xong rồi nó nó cấu hình store gọi hàm configureStore

configureStore: nó là một trong các bộ API của Redux, nó giúp ta cấu hình các store, đồng thời ta có thể thiết lập các middleware trong đây luôn

Middleware là gì? chúng ta cũng biết là middleware là một thằng đứng trung gian, nó có nhiệm vụ kiểm tra những thứ cần đúng theo yêu cầu mà ta cần đề ra, nếu mọi thứ đúng hết nó nó mới cho phép các phép toán tiếp theo. Hiểu đơn giản nhất là mọi yêu cầu của bạn cần gửi đi phải đi qua thằng này trước, nó thấy ok thì nó cho đi tiếp.

+ features/counter/counterSlice.js

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { fetchCount } from './counterAPI';

const initialState = {
  value: 0,
  status: 'idle',
};

// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
  'counter/fetchCount',
  async (amount) => {
    const response = await fetchCount(amount);
    // The value we return becomes the `fulfilled` action payload
    return response.data;
  }
);

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  // The `reducers` field lets us define reducers and generate associated actions
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    // Use the PayloadAction type to declare the contents of `action.payload`
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
  // The `extraReducers` field lets the slice handle actions defined elsewhere,
  // including actions generated by createAsyncThunk or in other slices.
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      });
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state) => state.counter.value;

// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd = (amount) => (dispatch, getState) => {
  const currentValue = selectCount(getState());
  if (currentValue % 2 === 1) {
    dispatch(incrementByAmount(amount));
  }
};

export default counterSlice.reducer;

Ta nhìn cái 1 đóng code trên ta thấy nó có import createAsyncThunk, createSlice từ @reduxjs/toolkit'

createAsyncThunk để tạo ra các async action. ta có thể viết fetch ở trong đây nhé

export const incrementAsync = createAsyncThunk(
  'counter/fetchCount',
  async (amount) => {
    const response = await fetchCount(amount);
    // The value we return becomes the `fulfilled` action payload
    return response.data;
  }
);

hoặc bạn có thể viết fetch api trực tiếp như sau:

export const login = createAsyncThunk(
  // Tên action
  'user/login',

  // Code async logic, tham số đầu tiên data là dữ liệu truyền vào khi gọi action
  async (data, { rejectWithValue }) => {
    // Gọi lên API backend
    const response = await fetch(
      'https://api.test/login',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      }
    );

    // Convert dữ liệu ra json
    const jsonData = await response.json();

    // Nếu bị lỗi thì reject
    if (response.status < 200 || response.status >= 300) {
      return rejectWithValue(jsonData);
    }

    // Còn không thì trả về dữ liệu
    return jsonData;
  }
);

+ features/counter/counterAPI.js: nó cũng là một file viết fetch api thôi, giống như mình mới nói bên trên, tại tách riêng ra cho nhìn gọn ràng hơn 

// A mock function to mimic making an async request for data
export function fetchCount(amount = 1) {
  return new Promise((resolve) =>
    setTimeout(() => resolve({ data: amount }), 500)
  );
}

Tiếp theo bạn thấy hàm createSlice : Trong Redux Toolkit được sử dụng nhằm để kết hợp giữa actions và reducers lại với nhau và đó chính là nhiệm vụ của slice và được thực thi bằng hàm createSlice

Giờ là cần tạo một số state để cho chức năng counter

const initialState = {
  value: 0,
  status: 'idle',
};

Ta chú ý chổ reducers, ta cần cài đặt các actions trong đây, nói chung nhìn đơn giản cũng dễ hiểu, nếu bạn nào có tìm hiểu qua Redux rồi, thì nhìn vào đoạn mã trên chắc sẽ hiểu thôi, nó kiểm tra các dispatch actions đến có nằm trong reducers không, có thì nó tiến hành update state

 reducers: {
    increment: (state) => {
     // cập nhật state khi ta actions dispatch(increment())
      state.value += 1;
    },
    decrement: (state) => {
     // khi dispatch(decrement()) 
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
    // khi dispatch(incrementByAmount(number))
      state.value += action.payload;
    },
  },

 Tiếp theo ta hãy xem đoạn code dưới đây nó xử lý rất hay đấy 

///Code logic xử lý async action
 extraReducers: (builder) => {
   //Bắt đầu thực hiện action incrementAsync (Promise pending)
    builder
      .addCase(incrementAsync.pending, (state) => {
       // nó đang chạy loading
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        // Khi thực hiện action  incrementAsync  thành công (Promise fulfilled)
        state.status = 'idle';
        state.value += action.payload;
      });
  },

Nhìn thấy nó hay nhĩ đúng không nào, tôi thấy code xử lý logic trên tuyệt vời, để nửa làm một ví dụ về cái này cho dễ hiểu hơn mới được

Để có thể gọi được các actions trong reducers ta cần exports nó ra như sau:

//export các actions để ta có thể gọi trong component
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// ta chỉ cần lấy giá trị value của nó
export const selectCount = (state) => state.counter.value;

Bây giờ ta sẽ sử dụng nó trong component như thế nào đây, ta hãy xem Counter.js trong đường dẫn features/counters/ 

Khai báo câu lệnh sau để có thể select state ra sử dụng và dispatch actions 

import { useSelector, useDispatch } from 'react-redux';
.....
function App(){
 // getState selectCount được khái báo trong file counterSlice.js
 const count = useSelector(selectCount);

 // using dispatch actions
  const dispatch = useDispatch();
}

Gọi các ra actions ta đã cấu hình trong counterSlide.js

//counterSlice.js
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// component Counter.js
import {
  decrement,
  increment,
  incrementByAmount,
  incrementAsync,
  incrementIfOdd,
  selectCount,
} from './counterSlice';

Còn lại là dispatch các actions để xử lý thôi

const [incrementAmount, setIncrementAmount] = useState('2');
const incrementValue = Number(incrementAmount) || 0;

///dispatch
dispatch(decrement())
dispatch(increment())
dispatch(incrementByAmount(incrementValue))
dispatch(incrementAsync(incrementValue))
dispatch(incrementIfOdd(incrementValue))

Okay tìm hiểu sơ qua đây thử, giờ ta thử chạy cái source lên xem có gì thú vị không nào

npm start

Bạn nào chưa cài plugin Redux thì vào đường link này cài nhé : Redux Devtools Cài đặt xong, bạn thử xem các lần dispatch action xem, nó như thế nào nhé, để bạn dễ hiểu hơn về nó

Bài Viết Liên Quan

Messsage

Nếu bạn thích chia sẻ của tôi, đừng quên nhấn nút !ĐĂNG KÝ