React / Hooks төрөлжүүлэх

Hooks төрөлжүүлэх

React hooks — useState, useRef, useReducer зэрэг — TypeScript-тэй маш сайн хослодог. Ихэнх тохиолдолд TypeScript өөрөө төрлийг таньдаг, гэхдээ зарим нөхцөлд тодорхой заавал зааж өгөх шаардлагатай. Энэ хичээлд хамгийн түгээмэл hooks-ын TypeScript хэрэглээг практикаар судална.

useState төрөлжүүлэх

Энгийн утгуудад TypeScript ихэвчлэн өөрөө таньдаг тул нэмэлт зүйл бичихгүй байж болно:

tsx
// TypeScript автоматаар string гэж таньдаг
const [name, setName] = useState("Болд");

// TypeScript автоматаар number гэж таньдаг
const [count, setCount] = useState(0);

// TypeScript автоматаар boolean гэж таньдаг
const [isOpen, setIsOpen] = useState(false);

Харин эхний утга нь null эсвэл undefined байвал төрлийг заавал тодорхой зааж өгнө:

tsx
interface User {
  id: number;
  name: string;
  email: string;
}

// User объект эсвэл null байна — <User | null> гэж заана
const [user, setUser] = useState<User | null>(null);

// Массив — эхний утга хоосон массив байвал заавал заана
const [courses, setCourses] = useState<string[]>([]);
const [users, setUsers] = useState<User[]>([]);

<User | null> гэдэг нь "энэ state User объект эсвэл null байж болно" гэсэн үг. Иймд user.name гэж ашиглахаасаа өмнө user !== null гэж шалгах шаардлагатайг TypeScript сануулна.

Нарийн state object

Олон талбартай state бол interface ашиглан тодорхойлох нь хамгийн цэвэр арга:

tsx
interface FormState {
  username: string;
  email: string;
  password: string;
  isLoading: boolean;
  error: string | null;
}

const initialState: FormState = {
  username: "",
  email: "",
  password: "",
  isLoading: false,
  error: null,
};

function RegisterForm() {
  const [form, setForm] = useState<FormState>(initialState);

  const handleChange = (field: keyof FormState, value: string) => {
    setForm((prev) => ({ ...prev, [field]: value }));
  };

  return (
    <form>
      <input
        value={form.username}
        onChange={(e) => handleChange("username", e.target.value)}
        placeholder="Нэвтрэх нэр"
      />
      <input
        type="email"
        value={form.email}
        onChange={(e) => handleChange("email", e.target.value)}
        placeholder="И-мэйл"
      />
      {form.error && <p className="error">{form.error}</p>}
    </form>
  );
}

keyof FormState гэдэг нь "username" | "email" | "password" | "isLoading" | "error" гэсэн утгатай — зөвхөн тухайн interface-н талбаруудыг зөвшөөрнө.

useRef төрөлжүүлэх

useRef хоёр нийтлэг хэрэглээтэй — DOM элемент барих болон дурын утга хадгалах:

tsx
import { useRef, useEffect } from "react";

function FocusInput() {
  // DOM элемент барих — HTMLInputElement төрлийг заана
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    // null эсэл шалгаад л ашиглана
    inputRef.current?.focus();
  }, []);

  return <input ref={inputRef} placeholder="Автоматаар фокус болно" />;
}

DOM-гүй, зүгээр утга хадгалахад:

tsx
function Timer() {
  // render хийхгүйгээр утга хадгалах — null-гүй, шууд утга
  const intervalRef = useRef<number>(0);
  const countRef = useRef<number>(0);

  const start = () => {
    intervalRef.current = window.setInterval(() => {
      countRef.current += 1;
      console.log("Тоолол:", countRef.current);
    }, 1000);
  };

  const stop = () => {
    clearInterval(intervalRef.current);
  };

  return (
    <div>
      <button onClick={start}>Эхлэх</button>
      <button onClick={stop}>Зогсоох</button>
    </div>
  );
}

useReducer төрөлжүүлэх

useReducer нь нарийн state логиктой тохиолдолд useState-н оронд хэрэглэгддэг. TypeScript-тэй хамт union type ашиглан action-уудыг тодорхойлно:

tsx
interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

interface CartState {
  items: CartItem[];
  total: number;
}

// Боломжит бүх action-уудыг union-оор тодорхойлно
type CartAction =
  | { type: "ADD_ITEM"; payload: CartItem }
  | { type: "REMOVE_ITEM"; payload: { id: number } }
  | { type: "CLEAR_CART" };

function cartReducer(state: CartState, action: CartAction): CartState {
  switch (action.type) {
    case "ADD_ITEM":
      return {
        ...state,
        items: [...state.items, action.payload],
        total: state.total + action.payload.price,
      };
    case "REMOVE_ITEM":
      return {
        ...state,
        items: state.items.filter((item) => item.id !== action.payload.id),
      };
    case "CLEAR_CART":
      return { items: [], total: 0 };
    default:
      return state;
  }
}

function Cart() {
  const [cart, dispatch] = useReducer(cartReducer, { items: [], total: 0 });

  return (
    <div>
      <p>Нийт: {cart.total}₮</p>
      <button onClick={() => dispatch({ type: "CLEAR_CART" })}>
        Сагс цэвэрлэх
      </button>
    </div>
  );
}

TypeScript action.type-ыг таньж, ADD_ITEM action-д action.payloadCartItem байна гэдгийг мэддэг — аль action-д ямар payload байхыг заавар харуулна.

Hooks-ыг TypeScript-тэй ашиглах нь эхэндээ нэмэлт код мэт санагдах ч алдаа буурч, код уншихад хялбар болдгийг бодит проектод мэдрэх болно.

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

Товч дарах, input бичих, маягт илгээх зэрэг хэрэглэгчийн үйл явдлуудыг (events) TypeScript-тэй хэрхэн зөв боловсруулахыг практикаар үзнэ.