Stori.
[MIDU MOBILE - TRAINING] REDUX
5 min read

[MIDU MOBILE - TRAINING] REDUX

Redux

1. Redux là gì?

Redux là một thư viện quản lý trạng thái (state management) cho ứng dụng web, phổ biến trong các ứng dụng phát triển dựa trên JavaScript và React. Nó giúp quản lý trạng thái của ứng dụng một cách dễ dàng và hiệu quả, đồng thời giúp tạo ra một luồng dữ liệu (data flow) đơn giản và dễ theo dõi.

2. Vấn đề gặp phải

Note image

3. Nguyên lý vận hành của Redux

Note image

4. Các thành phần của Redux

Redux bao gồm 3 thành phần chính: action, store và reducer:

4.1. Action

Action đơn giản là những Event được tạo ra bằng việc sử function và gửi data từ app lên store. Data có thể được gửi bằng nhiều cách như submit form, gọi API hoặc thao tác của User. Mỗi action của Redux để có một thuộc tính type để miêu tả loại action và thuộc tính payload chứa thông tinh được gửi lên store.

Để gọi một action từ bất cứ đầu trong app, Redux sử dụng method dispatch() để gửi action tới Redux store để xử lý thay đổi state.

4.2. Reducer

Vì Redux sử dụng dispatch() để thay đổi state. Tuy nhiên method này chỉ dùng để thông báo có sự thay đổi, thực tế nó không thay đổi state, các reducer mới là thứ nắm vai trò này.

Reducer là những function lấy state hiện tại và action vừa được dispatch để trả về state mới.

4.3. Store

Store chính là trái tim của Redux. Đây là "single source of truth" nắm giữ toàn bộ state của app và cung cấp những method để thao tác với state, dispatch action, ... Mỗi action được dispatch sẽ trả về state mới cho store bằng reducer.

5. Ưu điểm/Nhược điểm

Ưu điểm

  • Giúp State trở nên dễ đoán hơn
  • Reducer trong Redux sẽ đảm nhận vai trò thuần khiết, thế nên chúng sẽ trả về đúng kết quả nếu bạn thực hiện đúng hành động và đúng State.

  • Dễ duy trì mạng lưới
  • Hệ thống Redux được triển khai nghiêm ngặt để các mã Code trở nên có tổ chức hơn trong ứng dụng. Điều này giúp cho các User có thể hiểu và duy trì được hệ thống dễ dàng.

  • Cải thiện hiệu suất
  • Khi UI ràng buộc với thư viện React trrong nền tảng cũng đồng nghĩa rằng Redux có thể phát triển mạnh mẽ để tối ưu hiệu suất cho chính mình.

  • Test ứng dụng nhanh chóng
  • Nền tảng này sẽ hỗ trợ các nhà phát triển kiểm tra ứng dụng nhanh chóng khi cần.

    Nhược điểm

  • Độ phức tạp cao
  • Redux có rất nhiều chức năng. Đồng nghĩa với việc nó sẽ có hệ thống vận hành tương đối phức tạp.

  • Giới hạn thiết kế
  • Redux thường bị giới hạn về mặt thiết kế do nó có ít sự lựa chọn thay thế.

  • Thiếu Encapsulation
  • Dữ liệu sẽ không có chức năng Encapsulation trong Redux, vì thế vấn đề bảo mật cần được lưu ý khi sử dụng nền tảng này.

  • Sử dụng nhiều bộ nhớ
  • Do các State bất biến, thế nên khi chúng cập nhật, các Reducer phải trả về các State mới. Từ đó khiến cho hệ thống Redux ngốn nhiều bộ nhớ hơn bình thường.

    6. Khi nào nên sử dụng Redux?

  • Quản lý nhiều state phức tạp
  • Khi ứng dụng cần quản lý nhiều state phức tạp và chúng cần tương tác với nhau, việc sử dụng Redux sẽ giúp bạn quản lý các state dễ dàng hơn

  • State cần chia sẻ global
  • Khi nhiều component cần sử dụng chung 1 state, việc sử dụng Redux sẽ giúp các component truy cập dễ dàng hơn so với việc sử dụng props

  • Lưu lại lịch sử của state và actions
  • Redux cho phép theo dõi lịch sử của các state và actions, điều này rất hữu ích cho việc debug về sau. Redux còn có extension trên Chrome tên là Redux DevTools, giúp chúng ta có thể dễ dàng theo dõi lịch sử thay đổi của state và actions ngay trên trình duyệt.

    Lợi ích

  • Output đồng nhất, dễ đoán.
  • Với chỉ một "Source of Truth" (store), chúng ta sẽ gặp ít vấn đề trong việc sync state giữa các component với nhau hơn.

  • Khả năng maintain
  • Redux có bộ guideline hết sức chặt chẽ về cách tổ chức code, action làm gì, reducer làm việc gì,... Mọi thứ đều cụ thể và rõ ràng nên việc maintain sẽ dễ dàng hơn nhiều.

  • Khả năng scale
  • Như đã nói ở trên, với việc tổ chức code nghiêm ngặt và rõ ràng, việc scale project sẽ trở nên dễ dàng hơn nhiều.

    7. Lưu ý khi sử dụng Redux?

  • Giá trị lưu trong Redux về bản chất vẫn là state nên nó thừa hưởng mọi tính chất của state, chỉ khác ở chỗ nó là global state (state toàn cục) nên khi giá trị lưu trong Redux thay đổi thì tất cả component đang sử dụng giá trị đó cũng sẽ thay đổi theo.
  • Chuẩn hoá dữ liệu (Normalized State) trước khi lưu chúng vào Redux để đảm bảo hiệu suất của ứng dụng đạt ở mức cao.
  • Nên thực hiện việc xử lý logic ở các hàm bên ngoài, hạn chế xử lý logic phức tạp bên trong Redux, Redux chỉ nên sử dụng để lưu dữ liệu cuối cùng sau khi đã qua xử lý.
  • 8. Redux toolkit

    RTK là một thư viện giúp mình viết Redux tốt hơn, dễ hơn và đơn giản hơn (tiêu chuẩn để viết Redux).

    Ba vấn đề làm nền tảng ra đời RTK:

  • Việc cấu hình một store Redux quá phức tạp.
  • Phải thêm rất nhiều gói để Redux có thể làm bất kỳ điều gì hữu ích.
  • Redux đòi hỏi quá nhiều mã boilerplate (khai báo, cấu hình, ...)
  • configureStore()

  • Có sẵn Redux DevTools
  • Có sẵn redux-thunk để thực hiện async actions
  • // Khi chưa có Redux Toolkit
    // store.js
    import { createStore, applyMiddleware, compose } from 'redux';
    import thunkMiddleware from 'redux-thunk';
    import rootReducer from './reducers';
    // Enable to use redux dev tool in development mode
    const composeEnhancers = 'development' === process.env.NODE_ENV
      ? (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose)
      : compose;
    // Use redux-thunk as a redux middleware
    const enhancer = composeEnhancers(applyMiddleware(thunkMiddleware));
    const store = createStore(rootReducer, {}, enhancer);
    export default store;
    // Khi đã có redux toolkit 🤣
    // store.js
    import { configureStore } from '@reduxjs/toolkit'
    import rootReducer from './reducers'
    const store = configureStore({ reducer: rootReducer })

    createReducer()

    // Không có Redux Toolkit
    function counterReducer(state = 0, action) {
      switch (action.type) {
        case 'increment':
          return state + action.payload
        case 'decrement':
          return state - action.payload
        default:
          return state
       }
    }
    // Có Redux Toolkit
    // - Mỗi key là một case
    // - Không cần handle default case
    const counterReducer = createReducer(0, {
      increment: (state, action) => state + action.payload,
      decrement: (state, action) => state - action.payload
    })
    // Một điểm hay nữa là reducer có thể mutate data trực tiếp.
    // Bản chất bên dưới họ sử dụng thư viện Immerjs
    const todoReducer = createReducer([], {
      addTodo: (state, action) => {
        // 1. Có thể mutate data trực tiếp 🎉
        state.push(action.payload)
      },
      removeTodo: (state, action) => {
        // 2. Hoặc phải trả về state mới
        // CHỨ KO ĐƯỢC cả 1 và 2 nha 😎
        const newState = [...state];
        newState.splice(action.payload, 1);
        return newState;
      }
    })
    

    createAction()

    // Không có redux toolkit
    const INCREMENT = 'counter/increment'
    function increment(amount) {
       return {
         type: INCREMENT,
         payload: amount
       }
    }
    const action = increment(3)
    // { type: 'counter/increment', payload: 3 }
    // Có redux toolkit
    const increment = createAction('counter/increment')
    const action = increment(3)
    // returns { type: 'counter/increment', payload: 3 }
    console.log(increment.toString())
    // 'counter/increment'

    Các bạn đọc thêm những hàm sau nữa nhé 😉

  • createSlice(): https://redux-toolkit.js.org/api/createSlice
  • createSelector(): https://redux-toolkit.js.org/api/createSelector
  • createAsyncThunk(): https://redux-toolkit.js.org/api/createAsyncThunk
  • createEntityAdapter(): https://redux-toolkit.js.org/api/createEntityAdapter
  • Setup một ví dụ đơn giản sử dụng RTK

    // 1. Setup todo slice
    // todoSlice.js
    const todoSlice = createSlice({
      name: 'todos',
      initialState: [],
      reducers: {
        addPost(state, action) {
          state.push(action.payload);
        },
        removePost(state, action) {
          state.splice(action.payload, 1)
        }
      }
    });
    const { actions, reducer } = todoSlice;
    export const { addPost, removePost } = actions;
    export default reducer;
    // 2. Setup redux store
    // store.js
    import { configureStore } from '@reduxjs/toolkit';
    import todoSlice from 'features/todos/todoSlice';
    const store = configureStore({
      reducer: {
        todos: todoSlice
      },
    })
    // 3. Bind Redux Provider to App
    // src/index.js
    import { Provider } from 'react-redux';
    import store from './store';
    import App from './App';
    function Main() {
      return (
        <Provider store={store}>
          <App />
        </Provider>
      )
    }
    // 4. Using redux in component
    // todo.jsx
    import { useDispatch, useSelector } from 'react-redux';
    import { removeTodo } from 'features/todos/todoSlice';
    import {View, TouchableOpacity, Text} from 'react-native';
    function Todo() {
      const dispatch = useDispatch();
      const todoList = useSelector(state => state.todos);
      const handleTodoClick = (todo, idx) => {
        const action = removeTodo(idx);
        dispatch(action);
      }
      return (
        <View>
          {todoList.map((todo, idx) => (
            <TouchableOpacity key={todo.id} onPress={() => handleTodoClick(todo, idx)}>
              <Text>
    	          {todo.title}
              </Text>
            </TouchableOpacity>
          ))}
        </View>
      )
    }

    Share this article

    Share:
    Read Next Article