React / useReducer hook

useReducer hook

useState нь нэг, хоёр хэмжигдэхүүн бүхий энгийн state-д маш тохиромжтой. Гэвч state-уудын тоо нэмэгдэж, тэдгээр нь хоорондоо уялдаатай болох тусам код нарийн төвөгтэй болдог. useReducer бол энэ асуудлыг шийддэг hook — бүх state шинэчлэлтийн логикийг нэг reducer функцэд цуглуулдаг. Redux мэддэг бол танил ойлголт байх.

useReducer үндэс

useReducer нь гурван ойлголт дээр тулгуурладаг: state (одоогийн утга), action (юу болсон), reducer (яаж өөрчлөх):

jsx
import { useReducer } from "react";

// Reducer функц — state ба action авч шинэ state буцаана
function counterReducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    case "reset":
      return { count: 0 };
    default:
      return state;
  }
}

function Counter() {
  // useReducer(reducer, initialState)
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>Тоо: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+1</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-1</button>
      <button onClick={() => dispatch({ type: "reset" })}>Дахилт</button>
    </div>
  );
}

dispatch функц нь action объект авч reducer-т дамжуулна. Reducer шинэ state буцааж, component дахин рендер болно.

useState-тэй харьцуулах

Хэзээ useReducer руу шилжих вэ? Дараах нөхцөл биелбэл:

jsx
// ❌ useState — state-уудын тоо нэмэгдэхэд хяналтаас гарна
function TodoList() {
  const [todos, setTodos] = useState([]);
  const [filter, setFilter] = useState("all");
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);
  const [editingId, setEditingId] = useState(null);

  // Нэмэх үед isLoading, error, todos гурвыг хамт өөрчлөх хэрэгтэй
  async function addTodo(text) {
    setIsLoading(true);
    setError(null);
    // ...
  }
}
jsx
// ✅ useReducer — бүх логик нэг газарт, тодорхой
const initialState = {
  todos: [],
  filter: "all",
  isLoading: false,
  error: null,
  editingId: null,
};

function todoReducer(state, action) {
  switch (action.type) {
    case "FETCH_START":
      return { ...state, isLoading: true, error: null };
    case "FETCH_SUCCESS":
      return { ...state, isLoading: false, todos: action.payload };
    case "FETCH_ERROR":
      return { ...state, isLoading: false, error: action.payload };
    case "ADD_TODO":
      return { ...state, todos: [...state.todos, action.payload] };
    case "TOGGLE_TODO":
      return {
        ...state,
        todos: state.todos.map((t) =>
          t.id === action.payload ? { ...t, done: !t.done } : t,
        ),
      };
    case "SET_FILTER":
      return { ...state, filter: action.payload };
    default:
      return state;
  }
}

Бодит жишээ — Todo апп

Reducer ашиглан бүрэн ажиллагаатай todo апп:

jsx
import { useReducer } from "react";

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState);

  const visibleTodos = state.todos.filter((todo) => {
    if (state.filter === "active") return !todo.done;
    if (state.filter === "done") return todo.done;
    return true;
  });

  function handleAdd(e) {
    e.preventDefault();
    const text = e.target.elements.todo.value.trim();
    if (!text) return;

    dispatch({
      type: "ADD_TODO",
      payload: { id: Date.now(), text, done: false },
    });
    e.target.reset();
  }

  return (
    <div>
      <form onSubmit={handleAdd}>
        <input name="todo" placeholder="Шинэ даалгавар..." />
        <button type="submit">Нэмэх</button>
      </form>

      <div>
        {["all", "active", "done"].map((f) => (
          <button
            key={f}
            onClick={() => dispatch({ type: "SET_FILTER", payload: f })}
            style={{ fontWeight: state.filter === f ? "bold" : "normal" }}
          >
            {f === "all" ? "Бүгд" : f === "active" ? "Идэвхтэй" : "Дууссан"}
          </button>
        ))}
      </div>

      <ul>
        {visibleTodos.map((todo) => (
          <li
            key={todo.id}
            onClick={() => dispatch({ type: "TOGGLE_TODO", payload: todo.id })}
            style={{ textDecoration: todo.done ? "line-through" : "none" }}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

Context-тэй хослуулах

useReducer + useContext хослол бол React-д Redux-н хялбарчилсан хувилбар юм:

jsx
import { createContext, useContext, useReducer } from "react";

const StoreContext = createContext(null);

export function StoreProvider({ children }) {
  const [state, dispatch] = useReducer(todoReducer, initialState);

  return (
    <StoreContext.Provider value={{ state, dispatch }}>
      {children}
    </StoreContext.Provider>
  );
}

export function useStore() {
  return useContext(StoreContext);
}

// Дурын component-оос ашиглана
function AddButton() {
  const { dispatch } = useStore();

  return (
    <button
      onClick={() =>
        dispatch({
          type: "ADD_TODO",
          payload: { id: 1, text: "Шинэ", done: false },
        })
      }
    >
      Нэмэх
    </button>
  );
}

Дараагийн хичээлд:

Custom Hook бичих аргыг судална. Давтагдах логикийг дахин ашиглагдах hook болгон гаргах нь React-н хамгийн хүчирхэг техникүүдийн нэг юм.