Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. useState turlari
- 2.1 Tur chiqarish (inference) — ko'p hollarda yetarli
- 2.2 Bo'sh boshlanish: [] va null
- 2.3 Lazy init va tur
- 3. Union state: imkonsiz holatlarni yopish
- 3.1 Uch boolean — sakkiz holat
- 3.2 Discriminated union state
- 4. useRef turlari
- 4.1 DOM ref: useRef<HTMLInputElement>(null)
- 4.2 Qiymat ref: useRef(0)
- 4.3 React 19: argumentsiz useRef() — xato
- 5. useReducer: amallar — discriminated union
- 5.1 Amal turi
- 5.2 Noto'g'ri amal va unutilgan case
- 6. Context: createContext<T | null> va guard hook
- 6.1 Muammo: standart qiymat
- 6.2 Guard hook — tekshiruv bir joyda
- 6.3 Qachon null siz?
- 7. Qolgan hooklar: tur qayerdan keladi
- 7.1 useMemo va useCallback
- 7.2 useActionState va useOptimistic
- 8. Custom hook qaytish turi
- 9. Vazifalar qadami: kanondagi hook turlarini o'qish
- 10. Ko'p uchraydigan xatolar
- 11. Mashqlar
- 1-mashq (oson): turni tanlang
- 2-mashq (o'rta): union state
- 3-mashq (qiyin): reducer va guard hook
- 4-mashq: Vazifalar qadami
- 12. Real ishda
- Xulosa
- Manbalar
React + TS II: hooklar turlari — useState, useRef, useReducer va Context'da null tuzog'i
Qisqacha: Ko'p hooklar turini boshlang'ich qiymatdan o'zi chiqaradi:
useState(0)—number. Muammo boshlang'ich qiymat "bo'sh" bo'lganda chiqadi:useState([])—never[],useState(null)— faqatnull. Bunday joyda turni o'zingiz yozasiz:useState<string[]>([]),useState<string | null>(null). DOM ref —useRef<HTMLInputElement>(null), uningcurrenti doimnullbo'lishi mumkin. Reducer amallari — discriminated union, Context esacreateContext<T | null>(null)vanullni tekshiradigan guard hook bilan.
Bu darsda
useStateturini qachon TypeScript o'zi chiqarishini va qachon<T>yozish shartligini ajrata olasiz.- Bir nechta boolean o'rniga union state yozasiz va imkonsiz holatlarni tur darajasida yopasiz.
useRefning uch ko'rinishini (DOM, qiymat, React 19 dagi majburiy argument) to'g'ri turlaysiz.- Reducer amallarini discriminated union qilib,
neverbilan unutilgan amalni tsc'ga topdirasiz. createContext<T | null>va guard hook yozasiz,vazifalardagiuseTemani o'qiysiz.
Oldin bilishingiz kerak: React + TS I, useReducer, Context I, Discriminated union.
1. Nega bu kerak?
Oldingi darsda props'ga tur yozdik — komponentning "tashqi eshigi". Endi ichkariga kiramiz: komponent xotirasi, ya'ni hooklar.
Sardor buyurtmalar ro'yxatini yozdi. Ro'yxat bo'sh boshlanadi, keyin taomlar qo'shiladi:
const [items, setItems] = useState([]);
// …
setItems(["Osh"]);
// ❌ tsc: Type 'string' is not assignable to type 'never'Xabar qo'rqitadi: never nima, nega satr unga to'g'ri kelmaydi? Sardor useState<any>([]) deb yozib, xatoni "yo'qotdi". Endi tsc umuman tekshirmaydi — ro'yxatga son ham, obyekt ham tushadi.
Hayotiy o'xshatish: oshxonadagi bo'sh idish. Idishga yorliq yopishtirilmasa, oshpaz unga nima solish mumkinligini bilmaydi. TypeScript ham bo'sh massivga qarab "bu yerga hech narsa tushmaydi" (never) deb xulosa qiladi. Yechim — idishga oldindan yorliq yopishtirish: useState<string[]>([]). Bu darsda hooklarga yorliqni qachon va qanday yopishtirishni ko'ramiz.
2. useState turlari
2.1 Tur chiqarish (inference) — ko'p hollarda yetarli
Boshlang'ich qiymat "to'liq" bo'lsa, <T> yozish shart emas:
import { useState } from "react";
export default function App() {
const [count, setCount] = useState(0); // number
const [dish, setDish] = useState("Osh"); // string
const [open, setOpen] = useState(false); // boolean
return (
<div>
<p>
{dish} × {count} {open ? "(ochiq)" : "(yopiq)"}
</p>
<button onClick={() => setCount((c) => c + 1)}>+</button>{" "}
<button onClick={() => setDish("Manti")}>Manti</button>{" "}
<button onClick={() => setOpen((o) => !o)}>Ochish</button>
</div>
);
}Ekranda:
Osh × 0 (yopiq)
+ Manti OchishMuharrirda count ustiga sichqonchani olib borsangiz — number. setCount turi esa Dispatch<SetStateAction<number>>. Uzun nom, lekin ma'nosi oddiy. SetStateAction<number> — "yo son, yo (oldingi) => son funksiyasi" (snapshot va updater). Dispatch esa "shuni qabul qilib, hech narsa qaytarmaydigan funksiya".
2.2 Bo'sh boshlanish: [] va null
Ikki tuzoq — ikkalasi ham "bo'sh" boshlang'ich qiymatdan:
import { useState } from "react";
export default function App() {
const [items, setItems] = useState([]);
const [error, setError] = useState(null);
function add() {
setItems(["Osh"]);
setError("Stol tanlanmagan");
}
return <button onClick={add}>Qo'shish</button>;
}Xato:
TS2322: Type 'string' is not assignable to type 'never'.
TS2345: Argument of type '"Stol tanlanmagan"' is not assignable to parameter of type 'SetStateAction<null>'.- "
stringnineverga berib bo'lmaydi."[]dan TypeScriptnever[]chiqardi — "hech qanday element bo'lmaydigan massiv".never— hech qachon bo'lmaydigan qiymat turi. - "
"Stol tanlanmagan"niSetStateAction<null>ga berib bo'lmaydi."nulldan tur — faqatnull. Bu state hech qachon boshqa narsa bo'la olmaydi.
Tuzatish — idishga yorliq:
import { useState } from "react";
export default function App() {
const [items, setItems] = useState<string[]>([]);
const [error, setError] = useState<string | null>(null);
function add() {
if (items.length >= 2) {
setError("Ko'pi bilan 2 ta taom");
return;
}
setItems([...items, "Osh"]);
}
return (
<div>
<button onClick={add}>Qo'shish</button>
<p>{items.join(", ") || "Bo'sh"}</p>
{error !== null && <p role="alert">{error}</p>}
</div>
);
}Ekranda:
Qo'shish
Bo'shUch marta bosib ko'ring: ikki "Osh" qo'shiladi, uchinchisida xato chiqadi. error !== null && — tekshiruvdan keyin error turi string, null emas (Narrowing). any ga qochish shart bo'lmadi.
2.3 Lazy init va tur
Boshlang'ich qiymat funksiyadan kelsa (dangasa boshlash), tur funksiyaning qaytish turidan chiqadi:
const [draft] = useState(() => localStorage.getItem("qoralama"));
// draft: string | null — getItem shunday qaytaradilocalStorage.getItem — string | null, chunki kalit bo'lmasligi mumkin. Tur buni eslatib turadi: draft.length yozsangiz — 'draft' is possibly 'null'. vazifalar dagi TemaProvider ham shunday: useState(saqlanganTanlov) — funksiya, tur esa uning natijasidan.
Tekshirib ko'ring:
const [price, setPrice] = useState<number>()yozildi.priceturi qanday bo'ladi va nega?
Javob
number | undefined. Argument berilmagan — boshlang'ich qiymat undefined. @types/react da buning uchun alohida yozuv (overload) bor: argumentsiz useState<S>() turni S | undefined qiladi. Shuning uchun price.toFixed() — xato, avval price !== undefined tekshiruvi kerak. Agar qiymat boshidan ma'lum bo'lsa, useState(0) yozing — undefined holati umuman yo'qoladi.
3. Union state: imkonsiz holatlarni yopish
3.1 Uch boolean — sakkiz holat
Taomlar ro'yxatini yuklash. Birinchi urinish ko'pincha shunday bo'ladi:
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [dishes, setDishes] = useState<string[] | null>(null);Uch mustaqil state. Ular ichida "yuklanmoqda VA xato VA ma'lumot bor" kabi bema'ni kombinatsiya ham bor. Holat mashinasi darsida buni "imkonsiz holat" degan edik. U yerda bitta status satriga o'tgan edik. TypeScript bu fikrni yana bir qadam oldinga suradi: har holat o'zi bilan birga kerakli ma'lumotni olib yuradi.
3.2 Discriminated union state
import { useState } from "react";
type Load =
| { status: "idle" }
| { status: "loading" }
| { status: "ok"; dishes: string[] }
| { status: "error"; message: string };
export default function App() {
const [load, setLoad] = useState<Load>({ status: "idle" });
function fetchMenu() {
setLoad({ status: "loading" });
setTimeout(() => {
setLoad({ status: "ok", dishes: ["Osh", "Manti"] });
}, 200);
}
return (
<div>
<button onClick={fetchMenu}>Menyuni yuklash</button>
{load.status === "loading" && <p>Yuklanmoqda…</p>}
{load.status === "ok" && <p>{load.dishes.join(", ")}</p>}
{load.status === "error" && <p role="alert">{load.message}</p>}
</div>
);
}Ekranda:
Menyuni yuklashTugmani bosgandan keyin "Yuklanmoqda…", 200 ms dan keyin "Osh, Manti" chiqadi. Asosiy narsa — JSX ichidagi tekshiruvlar. load.status === "ok" dan keyin TypeScript load ning qaysi varianti ekanini biladi va load.dishes ga ruxsat beradi. "error" holatida esa dishes umuman yo'q:
type Load =
| { status: "loading" }
| { status: "ok"; dishes: string[] }
| { status: "error"; message: string };
function MenuView({ load }: { load: Load }) {
if (load.status === "error") {
return <p>{load.dishes.length} ta taom</p>;
}
return <p>{load.status}</p>;
}
export default function App() {
return <MenuView load={{ status: "loading" }} />;
}Xato:
TS2339: Property 'dishes' does not exist on type '{ status: "error"; message: string; }'."dishes xususiyati { status: "error"; message: string } turida yo'q." Uchta mustaqil state bilan bu xato faqat ish vaqtida, undefined.length bo'lib chiqardi. Holatlar va o'tishlarni diagrammada ko'ring:
stateDiagram-v2
[*] --> idle
idle --> loading: tugma
loading --> ok: javob keldi
loading --> error: server xatosi
error --> loading: qayta urinish
ok --> loading: yangilashHar holat — union'ning bitta varianti. ok da dishes bor, error da message bor, boshqalarida yo'q. Imkonsiz kombinatsiya yozib bo'lmaydi: { status: "loading", message: "…" } ham tsc xatosi.
4. useRef turlari
4.1 DOM ref: useRef<HTMLInputElement>(null)
useRef II darsida DOM elementga ref ulagan edik. Turi shunday yoziladi:
import { useRef } from "react";
export default function App() {
const inputRef = useRef<HTMLInputElement>(null);
function focusInput() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} aria-label="Taom" />
<button onClick={focusInput}>Yozish</button>
</div>
);
}Xato:
TS18047: 'inputRef.current' is possibly 'null'."inputRef.current null bo'lishi mumkin." To'g'ri ogohlantirish: birinchi render paytida element hali yo'q, unmount'dan keyin esa yana null. Ikki tuzatish bor:
import { useRef } from "react";
export default function App() {
const inputRef = useRef<HTMLInputElement>(null);
function focusInput() {
inputRef.current?.focus(); // null bo'lsa — hech narsa
console.log("fokus:", document.activeElement?.id);
}
return (
<div>
<input id="taom" ref={inputRef} aria-label="Taom" />
<button onClick={focusInput}>Yozish</button>
</div>
);
}Konsolda:
fokus: taomBirinchi tuzatish — ?. (optional chaining) — eng qisqa yo'l. Ikkinchi yo'l — if (inputRef.current === null) return; va undan keyin bemalol ishlatish. inputRef.current!.focus() (non-null !) ham ishlaydi, lekin u tsc'ga "menga ishon" deydi va ogohlantirishni o'chiradi. Bu assertion — xato bo'lsa, ish vaqtida yiqiladi.
Tur nomlari — brauzerdagi DOM sinflari: HTMLInputElement, HTMLButtonElement, HTMLDivElement, HTMLHeadingElement, HTMLDialogElement. Noto'g'ri tur bersangiz (useRef<HTMLDivElement> ni <input ref> ga) — tsc xato beradi.
4.2 Qiymat ref: useRef(0)
Ref DOM uchun emas, render chaqirmaydigan xotira uchun bo'lsa — boshlang'ich qiymatdan tur chiqadi:
import { useRef } from "react";
export default function App() {
const clicks = useRef(0); // RefObject<number>
function handleClick() {
clicks.current++;
console.log("bosishlar:", clicks.current);
}
return <button onClick={handleClick}>Bosish</button>;
}Konsolda:
bosishlar: 1
bosishlar: 2
bosishlar: 3Ekranda:
BosishTaymer id'si uchun: useRef<number | null>(null) — setTimeout brauzerda son qaytaradi, boshida esa taymer yo'q.
4.3 React 19: argumentsiz useRef() — xato
Eski kodda useRef<HTMLInputElement>() ko'p uchraydi. @types/react 19 da argument majburiy:
import { useRef } from "react";
export default function App() {
const ref = useRef<HTMLInputElement>();
return <input ref={ref} aria-label="Taom" />;
}Xato:
TS2554: Expected 1 arguments, but got 0."1 ta argument kutilgan, 0 ta berildi." React 19 gacha argumentsiz chaqiruv undefined bilan boshlanardi. Endi turlar bunga yo'l qo'ymaydi: React DOM ref'ni null bilan to'ldiradi, undefined bilan emas. Davosi bitta — useRef<HTMLInputElement>(null). React 18 → 19 ga ko'chishda bu eng ko'p uchraydigan tur xatolaridan biri (React 18 dan 19 ga).
Yana bir o'zgarish: React 19 turlarida RefObject ning current i o'zgartirsa bo'ladigan. Eski MutableRefObject endi kerak emas. Eski kodda ko'rsangiz — shunchaki RefObject ning eski nomi.
| Maqsad | Yozuv | current turi |
|---|---|---|
| DOM element | useRef<HTMLInputElement>(null) |
HTMLInputElement | null |
| oddiy qiymat | useRef(0) |
number |
| keyin to'ladigan qiymat | useRef<number | null>(null) |
number | null |
5. useReducer: amallar — discriminated union
5.1 Amal turi
useReducer darsida amallar { tur: "…" } obyektlari edi. TypeScript'da ularning hammasi bitta union'ga yig'iladi:
import { useReducer } from "react";
type Action =
| { type: "add"; dish: string }
| { type: "remove"; dish: string }
| { type: "clear" };
function orderReducer(state: string[], action: Action): string[] {
switch (action.type) {
case "add":
return [...state, action.dish];
case "remove":
return state.filter((d) => d !== action.dish);
case "clear":
return [];
default: {
const unhandled: never = action;
return unhandled;
}
}
}
export default function App() {
const [order, dispatch] = useReducer(orderReducer, []);
return (
<div>
<button onClick={() => dispatch({ type: "add", dish: "Osh" })}>
Osh
</button>{" "}
<button onClick={() => dispatch({ type: "clear" })}>
Tozalash
</button>
<p>{order.length} ta taom</p>
</div>
);
}Ekranda:
Osh Tozalash
0 ta taomUch narsa avtomatik ishlaydi:
useReducer(orderReducer, [])—<T>yozilmadi. State va amal turi reducer funksiyasining o'zidan olindi. Bo'sh[]bu yerda tuzoq emas: reducerstate: string[]deb aytdi.case "add":ichidaaction.dish— xatosiz, chunki bu tarmoqdaactionfaqat{ type: "add"; dish: string }.dispatchham turlangan: noto'g'ri amal yuborib bo'lmaydi.
5.2 Noto'g'ri amal va unutilgan case
import { useReducer } from "react";
type Action = { type: "add"; dish: string } | { type: "clear" };
function orderReducer(state: string[], action: Action): string[] {
switch (action.type) {
case "add":
return [...state, action.dish];
default: {
const unhandled: never = action;
return unhandled;
}
}
}
export default function App() {
const [order, dispatch] = useReducer(orderReducer, []);
dispatch({ type: "add" });
dispatch({ type: "remove", dish: "Osh" });
return <p>{order.length}</p>;
}Xato:
TS2322: Type '{ type: "clear"; }' is not assignable to type 'never'.
TS2345: Argument of type '{ type: "add"; }' is not assignable to parameter of type 'Action'.
Property 'dish' is missing in type '{ type: "add"; }' but required in type '{ type: "add"; dish: string; }'.
TS2322: Type '"remove"' is not assignable to type '"add" | "clear"'.- "
{ type: "clear" }nineverga berib bo'lmaydi" —case "clear"unutilgan.defaultga yetib kelgan amalneverbo'lishi kerak edi, lekinclearqolib ketdi. Bu — to'liqlik tekshiruvi. - "
addamalidadishyo'q" — yarim amal. - "
"remove"turi"add" | "clear"ga kirmaydi" — mavjud bo'lmagan amal.
Ikkinchi va uchinchi xato dispatch ni chaqirgan joyda — komponentda. Birinchisi reducer ichida. Ya'ni yangi amal turini qo'shsangiz, tsc sizni reducer'ga yuboradi: "buni ishlamading".
Tekshirib ko'ring:
defaultichidaconst unhandled: never = action;o'rnigareturn state;yozilsa, nima yo'qoladi?
Javob
Kod ishlaydi, lekin to'liqlik tekshiruvi yo'qoladi. Yangi amal ("remove") qo'shilib, case yozish unutilsa — tsc jim. Amal yuboriladi, reducer uni default da "yutib yuboradi", ekranda hech narsa o'zgarmaydi. never qatori shu jimlikni kompilyatsiya xatosiga aylantiradi.
6. Context: createContext<T | null> va guard hook
6.1 Muammo: standart qiymat
createContext bitta argument talab qiladi — Provider topilmasa ishlatiladigan standart qiymat. Murakkab qiymat uchun ({ tanlov, tanla }) "soxta" standart yasash yomon fikr: Provider unutilsa, xato jim yashirinadi. Shuning uchun standart — null, tur esa T | null:
import { createContext, use } from "react";
type Cart = { count: number; add: () => void };
const CartContext = createContext<Cart | null>(null);
function CartBadge() {
const cart = use(CartContext);
return <b>{cart.count}</b>;
}
export default function App() {
return <CartBadge />;
}Xato:
TS18047: 'cart' is possibly 'null'.TypeScript haq: App da Provider umuman yo'q. Har iste'molchida if (cart === null) yozish esa zerikarli va takrorlanadi.
6.2 Guard hook — tekshiruv bir joyda
Yechim — custom hook, u null ni bir marta tekshiradi va aniq xato tashlaydi:
import { createContext, use, useState } from "react";
import type { ReactNode } from "react";
type Cart = { count: number; add: () => void };
const CartContext = createContext<Cart | null>(null);
function CartProvider({ children }: { children: ReactNode }) {
const [count, setCount] = useState(0);
const value: Cart = { count, add: () => setCount((c) => c + 1) };
return <CartContext value={value}>{children}</CartContext>;
}
function useCart(): Cart {
const cart = use(CartContext);
if (cart === null) {
throw new Error("useCart() — <CartProvider> ichida chaqirilsin");
}
return cart;
}
function AddButton() {
const { add } = useCart();
return <button onClick={add}>Savatga</button>;
}
function CartBadge() {
const { count } = useCart();
return <b> Savatda: {count}</b>;
}
export default function App() {
return (
<CartProvider>
<AddButton />
<CartBadge />
</CartProvider>
);
}Ekranda:
Savatga Savatda: 0useCart() qaytish turi — Cart, null emas. Iste'molchilar toza: const { add } = useCart(). Provider unutilsa — konsolda (Vite dev serverida esa sahifa ustidagi xato oynasida ham) aniq gap chiqadi: useCart() — <CartProvider> ichida chaqirilsin. Jim undefined emas.
Provider'siz holat ham shunday ko'rinadi:
import { createContext, use } from "react";
type Cart = { count: number };
const CartContext = createContext<Cart | null>(null);
function useCart(): Cart {
const cart = use(CartContext);
if (cart === null) {
throw new Error("useCart() — <CartProvider> ichida chaqirilsin");
}
return cart;
}
function CartBadge() {
return <b>{useCart().count}</b>;
}
export default function App() {
return <CartBadge />;
}Konsolda:
Uncaught Error: useCart() — <CartProvider> ichida chaqirilsinXato dasturchiga, ishlab chiqish paytida chiqadi — ilova foydalanuvchiga yetib bormaydi. Bu yaxshi xato: nima qilish kerakligi matnda yozilgan.
6.3 Qachon null siz?
Context qiymati oddiy va standart qiymati ma'noli bo'lsa, null shart emas: createContext<"uz" | "ru">("uz") — Provider'siz ham til bor. Guard hook — Provider majburiy bo'lgan holat uchun: savat, auth, tema boshqaruvi. 42-darsdagi useAuth ham aynan shu shaklda bo'ladi (React'da auth holati).
7. Qolgan hooklar: tur qayerdan keladi
7.1 useMemo va useCallback
useMemo turi — funksiya nima qaytarsa, o'sha. Odatda hech narsa yozish shart emas. useCallback da esa bitta tuzoq bor: funksiya parametrlari turi chiqarilmaydi, chunki useCallback uni qayerga uzatishingizni bilmaydi:
import { useCallback, useMemo } from "react";
export default function App() {
const dishes = ["Osh", "Manti"];
const count = useMemo(() => dishes.length, [dishes]); // number
const pick = useCallback((dish) => console.log(dish), []);
return <button onClick={() => pick("Osh")}>{count}</button>;
}Xato:
TS7006: Parameter 'dish' implicitly has an 'any' type."dish parametri yashirincha any turini oldi." strict rejimda bu taqiqlangan. Tuzatish — parametrga tur: useCallback((dish: string) => …, []). Xuddi shu funksiya to'g'ridan-to'g'ri onClick={(e) => …} ga yozilsa, tur kerak emas edi: u yerda TypeScript kutilgan turni biladi. Bu "kontekstli tur" — keyingi darsda hodisalarda ko'p uchraydi.
7.2 useActionState va useOptimistic
18-qismdagi yangi hooklar ham turni funksiyadan oladi. useActionState — action funksiyasining (oldingi, FormData) => Promise<Holat> turidan:
type FormState = { error: string | null };
async function saveDish(
prev: FormState,
data: FormData,
): Promise<FormState> {
const name = data.get("dish"); // FormDataEntryValue | null
if (typeof name !== "string" || name === "") {
return { error: "Taom nomi kerak" };
}
return { error: null };
}
const [state, formAction, isPending] = useActionState(saveDish, {
error: null,
});
// state: FormState, formAction: (payload: FormData) => void,
// isPending: booleanIkki nozik joy. Birinchisi — data.get("dish") turi FormDataEntryValue | null, ya'ni satr, fayl yoki null. Shuning uchun typeof name !== "string" tekshiruvi shart. vazifalar dagi formalar ham shunday qiladi. Ikkinchisi — boshlang'ich qiymat { error: null } dan tur chiqarilmaydi, u action funksiyasidan olinadi. Aks holda yana null tuzog'i bo'lardi.
useOptimistic da tur — birinchi argumentdan (asosiy holat) va reducer'ning ikkinchi parametridan:
const [shown, addOptimistic] = useOptimistic(
dishes, // string[]
(current, dish: string) => [...current, dish],
);
// addOptimistic: (action: string) => voiddish: string — yozilishi shart, useCallback dagi sabab bilan. current ning turi esa dishes dan keladi.
| Hook | Tur qayerdan | Yozish kerakmi |
|---|---|---|
useMemo |
funksiya natijasi | yo'q |
useCallback |
funksiyaning o'zi | parametr turlari — ha |
useActionState |
action funksiyasi | parametr va qaytish turi |
useOptimistic |
asosiy holat + reducer | reducer'ning 2-parametri |
useTransition |
— | yo'q: [boolean, startTransition] |
8. Custom hook qaytish turi
Custom hook massiv qaytarsa, TypeScript uni tuple deb emas, oddiy massiv deb chiqaradi:
import { useState } from "react";
function useToggle(start: boolean) {
const [on, setOn] = useState(start);
const toggle = () => setOn((v) => !v);
return [on, toggle];
}
export default function App() {
const [open, toggle] = useToggle(false);
const label = open ? "Yopish" : "Ochish";
return <button onClick={toggle}>{label}</button>;
}Xato:
TS2322: Type 'boolean | (() => void)' is not assignable to type 'MouseEventHandler<HTMLButtonElement> | undefined'.
Type 'boolean' is not assignable to type 'MouseEventHandler<HTMLButtonElement>'.Qaytish turi — (boolean | (() => void))[]: "har element yo boolean, yo funksiya". toggle ham shunday aralash bo'lib qoldi. Ikki tuzatish:
import { useState } from "react";
function useToggle(start: boolean) {
const [on, setOn] = useState(start);
const toggle = () => setOn((v) => !v);
return [on, toggle] as const; // readonly [boolean, () => void]
}
export default function App() {
const [open, toggle] = useToggle(false);
const label = open ? "Yopish" : "Ochish";
return <button onClick={toggle}>{label}</button>;
}Ekranda:
Ochishas const — "bu aynan ikki elementli, tartibi qat'iy tuple". Ikkinchi yo'l — qaytish turini ochiq yozish: function useToggle(start: boolean): [boolean, () => void]. vazifalar dagi useBoshqariladiganHolat ikkinchi yo'lni tanlagan. Ikkitadan ko'p qiymat bo'lsa — massiv emas, obyekt qaytaring: { open, toggle, close } — nomlar bilan.
9. Vazifalar qadami: kanondagi hook turlarini o'qish
refactor/turlar branch'ida bugun kod o'zgarmaydi — bu darsdagi qoidalar vazifalar da 17-qismdan beri bor. Ularni topib o'qiymiz. Uchta fayl.
kontekst/TemaKonteksti.tsx — guard hook, kanondan aynan:
type TemaQiymati = {
tanlov: Tema;
tanla: (yangi: Tema) => void;
};
// null — Provider yo'q (dasturchi xatosi): useTema() aniq xato beradi
const TemaKonteksti = createContext<TemaQiymati | null>(null);
/** Tema konteksti. Provider ichida bo'lmasa — aniq xato
(null emas) */
export function useTema(): TemaQiymati {
// use() — useContext kabi, lekin shart ichida ham chaqirsa bo'ladi
const qiymat = use(TemaKonteksti);
if (qiymat === null) {
throw new Error("useTema() — <TemaProvider> ichida chaqirilsin");
}
return qiymat;
}Xuddi «Guard hook — tekshiruv bir joyda» bo'limidagi useCart. Ikkinchi nusxasi — kontekst/qator-amallari.ts dagi useQatorAmallari.
reducer.ts — amallar union'i (qisqartirilgan, kanonda 8 variant) va never:
export type VazifaAmali =
| { readonly tur: "qoshildi"; readonly matn: string }
| { readonly tur: "almashtirildi"; readonly id: number }
| { readonly tur: "ochirildi"; readonly id: number }
// … matnOzgardi, serverdanKeldi, royxatKeldi, tahrirBoshlandi
| { readonly tur: "tahrirYopildi" };
default: {
const qolmadi: never = amal;Amal nomlari — o'tgan zamonda ("qo'shildi", "o'chirildi"): amal "nima qil" buyrug'i emas, "nima bo'ldi" xabari. Har xususiyat readonly — reducer amalni o'zgartirmaydi.
ui/useMenyu.ts — ref va union state:
type FokusJoyi = "birinchi" | "oxirgi";
export type MenyuBoshqaruvi = ReturnType<typeof useMenyu>;
const tugmaRef = useRef<HTMLButtonElement>(null);
const royxatRef = useRef<HTMLDivElement>(null);
const [fokusJoyi, setFokusJoyi] = useState<FokusJoyi>("birinchi");useState<FokusJoyi> — <T> shart: usiz "birinchi" dan tur string chiqardi va setFokusJoyi("pastki") o'tib ketardi. MenyuBoshqaruvi esa hook qaytaradigan obyekt turini qo'lda yozmasdan oladi: ReturnType "funksiya nima qaytarsa — shu tur". Compound Menyu.tsx uni Context turi sifatida ishlatadi.
Tekshirish — npm run tip: 0 xato. Bugun commit yo'q.
10. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
Type 'string' is not assignable to type 'never' |
useState([]) |
useState<string[]>([]) |
SetStateAction<null> xatosi |
useState(null) |
useState<T | null>(null) |
'x.current' is possibly 'null' |
DOM ref | ?., if tekshiruvi |
TS2554: Expected 1 arguments |
useRef<T>() (React 19) |
useRef<T>(null) |
Type '{…}' is not assignable to type 'never' |
reducer'da case unutilgan |
case qo'shing |
'ctx' is possibly 'null' |
createContext<T | null> |
guard hook |
boolean | (() => void) |
hook massiv qaytaradi | as const yoki qaytish turi |
11. Mashqlar
Mashqlar kurs/mashqlar/18/35-hooklar-turlari/ da.
1-mashq (oson): turni tanlang
Har state uchun useState qatorini to'ldiring.
- Tanlangan stol raqami, boshida tanlanmagan:
useState<>(null) - Savatdagi taomlar nomlari, boshida bo'sh:
useState<]>([]) - Yetkazish usuli — faqat
"olib ketish"yoki"yetkazish", boshida olib ketish:useState<>("olib ketish")
Yechim
number | null— "tanlanmagan" holatinull, keyin son.0bilan boshlash xato bo'lardi: 0-stol yo'q, lekin tur buni farqlay olmaydi.string[]— bo'sh massivga yorliq, aks holdanever[]."olib ketish" | "yetkazish"— literal union. Usiz"olib ketish"danstringchiqadi va istalgan satr o'tadi.
2-mashq (o'rta): union state
Ikki tugmali buyurtma: «Yuborish» va «Xato bilan yuborish». State — type Send = { status: "idle" } | { status: "sent"; id: number } | { status: "failed"; reason: string }. «Yuborish» — sent, id 101; «Xato bilan yuborish» — failed, sabab "Internet yo'q". Ekranda: sent — Buyurtma #101 qabul qilindi, failed — role="alert" da sabab, idle — hech narsa. Ishora: «Discriminated union state» bo'limidagi load.status === … tekshiruvlari.
Yechim
import { useState } from "react";
type Send =
| { status: "idle" }
| { status: "sent"; id: number }
| { status: "failed"; reason: string };
export default function App() {
const [send, setSend] = useState<Send>({ status: "idle" });
return (
<div>
<button onClick={() => setSend({ status: "sent", id: 101 })}>
Yuborish
</button>{" "}
<button
onClick={() =>
setSend({ status: "failed", reason: "Internet yo'q" })
}
>
Xato bilan yuborish
</button>
{send.status === "sent" && (
<p>Buyurtma #{send.id} qabul qilindi</p>
)}
{send.status === "failed" && (
<p role="alert">{send.reason}</p>
)}
</div>
);
}Ekranda:
Yuborish Xato bilan yuborish
Internet yo'qsend.id faqat status === "sent" tarmog'ida o'qiladi — boshqa joyda yozsangiz, TS2339. useState<Send> dagi <Send> shart: usiz { status: "idle" } dan tur { status: string } chiqardi va setSend({ status: "sent", id: 101 }) xato berardi.
3-mashq (qiyin): reducer va guard hook
Stol bron qilish konteksti yozing:
type Action = { type: "book"; table: number } | { type: "cancel" }, state —number | null(bron qilingan stol).- Reducer'da
neverbilan to'liqlik tekshiruvi. BookingContext—createContext<… | null>(null), qiymati{ table, dispatch }. Guard hook —useBooking().Appda Provider ichida ikki komponent:BookButton(bosilsa — 5-stol) vaStatus(5-stol bron qilindiyokiBron yo'q).
Ishora: dispatch turi — useReducer dan keladi, uni Dispatch<Action> deb yozish mumkin (import type { Dispatch } from "react").
Yechim
import { createContext, use, useReducer } from "react";
import type { Dispatch, ReactNode } from "react";
type Action = { type: "book"; table: number } | { type: "cancel" };
function bookingReducer(state: number | null, action: Action) {
switch (action.type) {
case "book":
return action.table;
case "cancel":
return null;
default: {
const unhandled: never = action;
return unhandled;
}
}
}
type Booking = { table: number | null; dispatch: Dispatch<Action> };
const BookingContext = createContext<Booking | null>(null);
function useBooking(): Booking {
const booking = use(BookingContext);
if (booking === null) {
throw new Error("useBooking() — <BookingProvider> ichida");
}
return booking;
}
function BookingProvider({ children }: { children: ReactNode }) {
const [table, dispatch] = useReducer(bookingReducer, null);
return (
<BookingContext value={{ table, dispatch }}>
{children}
</BookingContext>
);
}
function BookButton() {
const { dispatch } = useBooking();
return (
<button onClick={() => dispatch({ type: "book", table: 5 })}>
5-stolni bron qilish
</button>
);
}
function Status() {
const { table } = useBooking();
const text =
table === null ? "Bron yo'q" : `${table}-stol bron qilindi`;
return <p>{text}</p>;
}
export default function App() {
return (
<BookingProvider>
<BookButton />
<Status />
</BookingProvider>
);
}Ekranda:
5-stolni bron qilish
5-stol bron qilindiReducer'ning qaytish turi yozilmagan — tsc uni number | null deb chiqardi (action.table — son, null, never). useReducer(bookingReducer, null) da null ham tuzoq emas: state turi reducer'dan olinadi. Dispatch<Action> — "Action qabul qilib, hech narsa qaytarmaydigan funksiya", aynan useReducer bergan dispatch.
4-mashq: Vazifalar qadami
Loyihada uchta joyni toping va har biriga bir gap yozing:
createContext<… | null>va uning guard hook'i (ikkita bor).useStatega<T>ochiq berilgan joy va nega shart.neverbilan to'liqlik tekshiruvi (reducer.tsdan tashqari yana bitta bor).
Yechim
TemaKonteksti.tsx→useTema();qator-amallari.ts→useQatorAmallari().useMenyu.ts:useState<FokusJoyi>("birinchi")— usiz turstringbo'lardi. Boshqa misol: forma xatosiuseState<string | null>(null)(17-qism).reducer.tsdagiconst qolmadi: never = amal;vaApp.tsxdagi boshqa tab xabarlari switch'i (SinxronXabar). Ikkinchisi 42-darsda yangi xabar turi qo'shilganda haqiqatan ishga tushadi.npm run tip— 0 xato, commit yo'q.
12. Real ishda
- Union state — so'rov holati uchun eng keng tarqalgan naqsh. TanStack Query ham natijani shunday beradi:
status: "pending" | "error" | "success"(TanStack Query I). - Guard hook — deyarli har kutubxonada:
useRouter,useForm,useThemeProvider'siz chaqirilsa aniq xato beradi. neverbilan to'liqlik — Redux Toolkit, XState va reducer'li har loyihada. Kod ko'rigidadefault: return stateko'rilsa — "never tekshiruvi qani?" degan savol tug'iladi.- Intervyu savollari: "
useState([])neganever[]?", "useRef<T>(null)vauseRef<T>()farqi?", "Context'ganullstandart qiymat nega?".
Xulosa
useStateturi boshlang'ich qiymatdan chiqadi; bo'sh[]vanullda —<T>ochiq:useState<string[]>([]),useState<T | null>(null).- Bir nechta boolean o'rniga — discriminated union state: imkonsiz holatni yozib bo'lmaydi.
- DOM ref —
useRef<HTMLInputElement>(null),current—T | null; React 19 da argument majburiy. - Reducer amallari — union,
defaultdanever— unutilgancasetsc xatosi. - Context —
createContext<T | null>(null)+ guard hook; hook massiv qaytarsa —as const. vazifalar:useTema,VazifaAmalivauseMenyu— shu qoidalar 17-qismdan beri; bugun kod o'zgarmadi.
Keyingi dars: React + TS III: hodisalar va HTML atributlari — hodisa turlari va native element props'larini meros qilib olish.
Manbalar
- React: "Using TypeScript — Typing Hooks" — react.dev/learn/typescript (2026-10 holatiga, React 19.3)
- React 19 Upgrade Guide: "useRef requires an argument" — react.dev/blog/2024/04/25/react-19-upgrade-guide
- TypeScript: Narrowing — Discriminated unions,
never— typescriptlang.org/docs/handbook/2/narrowing.html @types/react19.3.0 —useState,useRef,useReducer,createContextta'riflari
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!