DEV Community

c_nooknook_daily_prompt
c_nooknook_daily_prompt

Posted on

useReducer , Redux, Redux Toolkit (RTK)

useReducer

useReducer คือ Hook ที่ใช้จัดการ state แบบที่มี หลายสถานะ หรือ logic ซับซ้อน
คล้าย Redux ย่อส่วน ใช้คู่กับ dispatch และ reducer function

ตัวอย่างง่าย ๆ: Counter

import { useReducer } from 'react';

function counterReducer(state: number, action: { type: string }) {
  switch (action.type) {
    case 'increment':
      return state + 1;
    case 'decrement':
      return state - 1;
    default:
      return state;
  }
}

export default function Counter() {
  const [count, dispatch] = useReducer(counterReducer, 0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>เพิ่ม</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>ลด</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

โครงสร้างจำง่าย:

const [state, dispatch] = useReducer(reducerFunction, initialState);
Enter fullscreen mode Exit fullscreen mode
  • state → ค่าปัจจุบัน
  • dispatch(action) → ส่ง action ไปให้ reducer
  • reducer(state, action) → ฟังก์ชันที่กำหนดว่า state จะเปลี่ยนยังไง

ใช้เมื่อไหร่ดี?

  • ต้องจัดการ state ที่มีหลายประเภท เช่น เพิ่ม, ลบ, รีเซ็ต
  • ต้องการแยก logic ออกจาก component (เช่น form, cart, etc.)
  • ไม่อยากใช้ Redux แต่ logic คล้ายกัน

ประกาศประเภท State และ Action

type State = {
  count: number;
  showCount: boolean;
};

type Action =
  | { type: 'increment' }
  | { type: 'decrement' }
  | { type: 'reset' }
  | { type: 'toggle' };
Enter fullscreen mode Exit fullscreen mode

เขียน reducer ฟังก์ชัน

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'decrement':
      return { ...state, count: state.count - 1 };
    case 'reset':
      return { ...state, count: 0 };
    case 'toggle':
      return { ...state, showCount: !state.showCount };
    default:
      return state;
  }
}
Enter fullscreen mode Exit fullscreen mode

ใช้งานในคอมโพเนนต์

import React, { useReducer } from 'react';

const initialState: State = {
  count: 0,
  showCount: true,
};

export default function CounterReducer() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <h2>useReducer แบบ Object State + TypeScript</h2>

      <button onClick={() => dispatch({ type: 'toggle' })}>
        {state.showCount ? 'ซ่อน' : 'แสดง'} ตัวเลข
      </button>

      {state.showCount && <p>Count: {state.count}</p>}

      <button onClick={() => dispatch({ type: 'increment' })}>เพิ่ม</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>ลด</button>
      <button onClick={() => dispatch({ type: 'reset' })}>รีเซ็ต</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

สรุปจำง่าย

ส่วนประกอบ ทำหน้าที่อะไร
State โครงสร้างข้อมูลที่เราจะเก็บ
Action ชนิดของคำสั่งที่ส่งเข้า reducer
reducer() ฟังก์ชันหลักที่กำหนดว่า state เปลี่ยนยังไง
dispatch() ใช้ส่ง action เข้า reducer

Redux

Redux แบบดั้งเดิม (Vanilla Redux) เพื่อความเข้าใจ หลักการพื้นฐานจริง ๆ ก่อนใช้ Redux Toolkit
เราจะมาเริ่มจากโค้ดพื้นฐานที่ใช้แนวคิด action → reducer → store → dispatch ค่ะ

ติดตั้ง redux และ react-redux

npm install redux react-redux

Enter fullscreen mode Exit fullscreen mode

สร้าง Action Type และ Action Creator

// action types
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const RESET = 'RESET';

// action creators
export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
export const reset = () => ({ type: RESET });
Enter fullscreen mode Exit fullscreen mode

สร้าง Reducer

src/redux/reducer.js

import { INCREMENT, DECREMENT, RESET } from './actions';

const initialState = {
  count: 0,
};

export const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + 1 };
    case DECREMENT:
      return { ...state, count: state.count - 1 };
    case RESET:
      return { ...state, count: 0 };
    default:
      return state;
  }
};

Enter fullscreen mode Exit fullscreen mode

สร้าง Store

import { createStore } from 'redux';
import { counterReducer } from './reducer';

export const store = createStore(counterReducer);

Enter fullscreen mode Exit fullscreen mode

ครอบ ด้วย

src/main.jsx (หรือ index.js)

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import { store } from './redux/store';

ReactDOM.createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <App />
  </Provider>
);

Enter fullscreen mode Exit fullscreen mode

ใช้ใน Component

src/App.jsx

import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, reset } from './redux/actions';

export default function App() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={() => dispatch(increment())}>เพิ่ม</button>
      <button onClick={() => dispatch(decrement())}>ลด</button>
      <button onClick={() => dispatch(reset())}>รีเซ็ต</button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

สรุปโครงสร้าง Redux ดั้งเดิม

ส่วนประกอบ หน้าที่
action สิ่งที่บอกว่า "จะทำอะไร" (type: 'INCREMENT')
reducer รับ state + action → คืนค่า state ใหม่
store ที่รวม state และจัดการ dispatch, subscribe ฯลฯ
dispatch() ส่ง action ไปให้ reducer
Provider ครอบแอปเพื่อให้ทุก component ใช้ store ได้
useSelector() ดึงค่า state จาก store
useDispatch() ส่ง action เข้าสู่ store

Redux Toolkit (RTK)

เริ่มต้นใช้ Redux ง่าย ๆ แนะนำให้เริ่มจาก Redux Toolkit (RTK) ซึ่งเป็นวิธีที่ทีม Redux แนะนำให้ใช้เป็นหลักในปัจจุบัน เพราะลดความซับซ้อนและโค้ดเยอะจาก Redux แบบดั้งเดิม

เป้าหมาย: "สร้าง Counter App ด้วย Redux Toolkit"

ขั้นตอนง่าย ๆ (แบบ Step-by-Step)

ติดตั้ง Redux Toolkit + React Redux

npm install @reduxjs/toolkit react-redux

Enter fullscreen mode Exit fullscreen mode

สร้าง store

src/store.ts

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

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

// สำหรับใช้ใน TypeScript
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Enter fullscreen mode Exit fullscreen mode

สร้าง slice

src/counterSlice.ts

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

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
  },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    reset: (state) => {
      state.value = 0;
    },
  },
});

export const { increment, decrement, reset } = counterSlice.actions;
export default counterSlice.reducer;

Enter fullscreen mode Exit fullscreen mode

ครอบแอปด้วย Provider

src/main.tsx หรือ src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from 'react-redux';
import { store } from './store';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <Provider store={store}>
    <App />
  </Provider>
);

Enter fullscreen mode Exit fullscreen mode

ใช้ใน Component

src/App.tsx

import { useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from './store';
import { increment, decrement, reset } from './counterSlice';

export default function App() {
  const count = useSelector((state: RootState) => state.counter.value);
  const dispatch: AppDispatch = useDispatch();

  return (
    <div>
      <h1>นับเลข: {count}</h1>
      <button onClick={() => dispatch(increment())}>เพิ่ม</button>
      <button onClick={() => dispatch(decrement())}>ลด</button>
      <button onClick={() => dispatch(reset())}>รีเซ็ต</button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

สรุปจำง่าย

สิ่งที่ทำ ชื่อไฟล์
สร้าง state + action counterSlice.ts
รวม reducer store.ts
ครอบ Provider main.tsx
ใช้งานในหน้า UI App.tsx

เคล็ดลับ

  • RTK ใช้ Immer อยู่แล้ว → สามารถ "แก้ state ตรง ๆ" ได้
  • ใช้ useSelector() เพื่ออ่านค่า
  • ใช้ useDispatch() เพื่อเรียก action ไปเปลี่ยนค่า

Top comments (0)