React
Ինտերֆեյսներ կառուցիր կոմպոնենտներից՝ state, hook-եր, rendering և React 19-ի server հնարավորությունները։
30 վայրկյանում
React-ը JavaScript գրադարան է՝ օգտատիրոջ ինտերֆեյսներ կառուցելու համար փոքր, վերաօգտագործվող մասերից, որոնք կոչվում են կոմպոնենտներ։ Դու նկարագրում ես, թե ինչպիսին պետք է լինի էկրանը տվյալ state-ի համար, և React-ը արդյունավետ թարմացնում է էջը, երբ այդ state-ը փոխվում է։ React 19-ը ավելացնում է Action-ներ ձևերի համար, use hook-ը և Server Component-ներ, որոնք render են լինում սերվերում և բրաուզերին ավելի քիչ JavaScript են ուղարկում։
Ինչու եմ օգտագործում
- React օգտագործում եմ իմ կառուցած գրեթե յուրաքանչյուր ինտերֆեյսի համար՝ Next.js խանութներ, ադմին պանելներ, Electron դրամարկղ և React Native-ի միջոցով՝ մոբայլ հավելվածներ։
- Ինձ դուր է գալիս, որ կոմպոնենտային մոդելը scale է անում. նույն pattern-ներն աշխատում են և՛ մեկ կոճակի, և՛ տասնյակ էկրաններով ադմին համակարգի համար։
- Սպասարկում եմ վերաօգտագործվող React փաթեթներ, այդ թվում config-driven ադմին համակարգ և account ու cart մոդուլներ, որոնք կիսվում են բազմաթիվ հաճախորդների կայքերի միջև։
- State-ի գործիքներն ընտրում եմ գիտակցաբար. նախ լոկալ state, Zustand՝ client state-ի համար, և server-cache գրադարան, ինչպես TanStack Query-ն, API տվյալների համար։
Ինչպես է աշխատում
01Կոմպոնենտներ, props և state
Կոմպոնենտը ֆունկցիա է, որը վերադարձնում է JSX՝ սինտաքս, որը նման է HTML-ի և նկարագրում է UI։ Կոմպոնենտները միավորվում են խորանարդիկների պես. ProductCard-ը ProductGrid-ի ներսում, որն էլ ShopPage-ի ներսում։ Props-ը այն մուտքերն են, որոնք ծնողը փոխանցում է երեխային. երեխայի տեսանկյունից դրանք միայն կարդալու համար են, ինչպես ֆունկցիայի արգումենտները։ State-ը տվյալներ են, որոնք կոմպոնենտը տիրապետում է և կարող է փոխել, օրինակ՝ արդյոք մենյուն բաց է, կամ ինչ է մուտքագրված դաշտում։ Երբ state-ը փոխվում է, React-ը re-render է անում այդ կոմպոնենտը և նրա երեխաներին՝ նոր արժեքներով։ Հիմնական գաղափարը «UI-ը state-ի ֆունկցիա է» սկզբունքն է. երբեք DOM-ը ձեռքով չես փոփոխում, փոխում ես state-ը և նկարագրում արդյունքը։ Տվյալները հոսում են ներքև props-ի միջոցով, իսկ փոփոխությունները՝ վեր՝ callback props-ի միջոցով, ինչպես onChange-ը։ Երբ երկու կոմպոնենտի պետք է նույն state-ը, բարձրացրու այն նրանց ամենամոտ ընդհանուր ծնողի մոտ։
type Product = { id: string; name: string; price: number };
function ProductCard({ product, onAdd }: { product: Product; onAdd: (id: string) => void }) {
return (
<article>
<h3>{product.name}</h3>
<p>{(product.price / 100).toFixed(2)} USD</p>
<button onClick={() => onAdd(product.id)}>Add to cart</button>
</article>
);
}
function ProductGrid({ products }: { products: Product[] }) {
const [cart, setCart] = useState<string[]>([]);
const add = (id: string) => setCart((prev) => [...prev, id]); // never mutate
return (
<section>
<p>{cart.length} items in cart</p>
{products.map((p) => <ProductCard key={p.id} product={p} onAdd={add} />)}
</section>
);
}
02Hook-եր. useState, useEffect, useMemo, useRef
Hook-երը ֆունկցիաներ են, որոնք կոմպոնենտներին տալիս են հիշողություն և side effect-ներ։ Կանչիր դրանք կոմպոնենտի վերին մակարդակում, երբեք պայմանների կամ ցիկլերի ներսում, որովհետև React-ը դրանց հետևում է կանչերի հերթականությամբ։ useState-ը պահում է արժեք և setter. օգտագործիր updater ձևը (setCount(c => c + 1)), երբ նոր արժեքը կախված է հնից։ useEffect-ը կոմպոնենտը սինխրոնացնում է React-ից դուրս ինչ-որ բանի հետ, օրինակ՝ subscription-ի, թայմերի կամ բրաուզերի API-ի. վերադարձրու cleanup ֆունկցիա և dependency array-ում թվարկիր այն բոլոր արժեքները, որոնք այն օգտագործում է։ Շատ effect-ներ ավելորդ են. եթե կարող ես արժեքը հաշվարկել render-ի ընթացքում, արա դա՝ այն state-ի մեջ սինխրոնացնելու փոխարեն։ useMemo-ն cache է անում թանկ հաշվարկը render-ների միջև, իսկ useCallback-ը՝ ֆունկցիան։ useRef-ը պահում է փոփոխվող արժեք, որը re-render չի առաջացնում, ամենից հաճախ՝ DOM node-ի հղում։ React 19.2-ը ավելացնում է useEffectEvent՝ effect-ի ներսում վերջին արժեքները կարդալու համար՝ առանց այն նորից աշխատեցնելու։
function SearchBox({ items }: { items: string[] }) {
const [query, setQuery] = useState("");
const inputRef = useRef<HTMLInputElement>(null);
// Derived during render: no extra state, no effect
const results = useMemo(
() => items.filter((i) => i.toLowerCase().includes(query.toLowerCase())),
[items, query]
);
// Side effect with cleanup: keyboard shortcut "/" focuses the input
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === "/") { e.preventDefault(); inputRef.current?.focus(); }
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, []);
return (<><input ref={inputRef} value={query} onChange={(e) => setQuery(e.target.value)} />
<ul>{results.map((r) => <li key={r}>{r}</li>)}</ul></>);
}
03Rendering և reconciliation
React-ում rendering նշանակում է կանչել քո կոմպոնենտ ֆունկցիաները՝ UI-ի նկարագրությունը ստանալու համար։ Դա չի նշանակում DOM-ին դիպչել։ Հետո React-ը համեմատում է նոր նկարագրությունը նախորդի հետ (reconciliation) և commit փուլում իրական DOM-ի վրա կիրառում է միայն տարբերությունները։ Կոմպոնենտը re-render է լինում, երբ փոխվում է նրա state-ը, երբ re-render է լինում նրա ծնողը, կամ երբ փոխվում է նրա կարդացած context-ը։ Re-render-ները սովորաբար էժան են. դանդաղները գալիս են թանկ հաշվարկներից կամ շատ մեծ ծառերից։ Key-երը React-ին ասում են, թե ցուցակի որ տարրը որն է. օգտագործիր կայուն id, ոչ թե array-ի index, այլապես React-ը կարող է վերաօգտագործել սխալ կոմպոնենտը և խառնել դրա state-ը։ Եթե որևէ դիրքում տարրի տիպը փոխվում է, React-ը դեն է նետում հին ենթածառը և դրա state-ը։ Rendering-ը պետք է մաքուր լինի. ոչ մի side effect, ոչ մի props-ի կամ state-ի mutation։ React Compiler-ը (stable 2025-ի վերջից) կարող է կոմպոնենտները memoise անել ավտոմատ՝ նվազեցնելով ձեռքով useMemo-ի և memo-ի կարիքը։
04Որտեղ պետք է ապրի state-ը
State-ի բագերի մեծ մասը գալիս է state-ը սխալ տեղում դնելուց։ Մտածիր state-ի տեսակներով։ Լոկալ UI state-ը (բաց dropdown, ձևի դաշտ) պատկանում է կոմպոնենտի ներսի useState-ին։ Ենթածառի կողմից կիսվող state-ը կամ հազվադեպ փոխվող արժեքները, ինչպես theme-ը, locale-ը կամ ընթացիկ օգտատերը, հարմար են Context-ի համար, բայց խուսափիր հաճախ փոխվող արժեքները մեկ մեծ context-ում դնելուց, որովհետև յուրաքանչյուր consumer re-render է լինում։ Գլոբալ client state-ը, ինչպես cart-ը, ֆիլտրերը կամ բազմաքայլ wizard-ը, ավելի պարզ է փոքր store-ում, ինչպես Zustand-ը, որտեղ կոմպոնենտները subscribe են անում միայն իրենց օգտագործած հատվածին։ Server state-ը, այսինքն քո API-ում ապրող տվյալները, տարբեր է. դրան պետք է caching, loading և error վիճակներ, refetch և invalidation։ Client կոմպոնենտներում դրա համար օգտագործիր TanStack Query կամ SWR, կամ fetch արա Server Component-ներում։ URL state-ը (որոնում, էջ, ֆիլտրեր) պատկանում է URL-ին, որպեսզի այն հնարավոր լինի կիսել և bookmark անել։
// Zustand: client state, components subscribe to slices
import { create } from "zustand";
type CartState = { items: string[]; add: (id: string) => void };
export const useCart = create<CartState>((set) => ({
items: [],
add: (id) => set((s) => ({ items: [...s.items, id] })),
}));
// TanStack Query: server state with caching and refetching
import { useQuery } from "@tanstack/react-query";
function Orders() {
const count = useCart((s) => s.items.length); // re-renders only on count change
const { data, isPending, error } = useQuery({
queryKey: ["orders"],
queryFn: () => fetch("/api/orders").then((r) => r.json()),
});
if (isPending) return <p>Loading...</p>;
if (error) return <p>Could not load orders.</p>;
return <p>{data.length} orders, {count} items in cart</p>;
}
05React 19. Action-ներ, useActionState և use
React 19-ը (թողարկված 2024-ի դեկտեմբերին, որին հաջորդեցին 19.1-ը և 19.2-ը) ասինխրոն UI-ը շատ ավելի պարզ դարձրեց։ Action-ը async ֆունկցիա է, որն օգտագործվում է transition-ում, ամենից տեսանելի՝ որպես ձևի action prop. React-ը հետևում է pending վիճակին, մշակում է սխալները և հաջողությունից հետո reset է անում ձևը։ useActionState-ը փաթաթում է action-ը և տալիս է դրա վերջին արդյունքը և isPending դրոշակ, ինչը իդեալական է ձևի վալիդացիայի հաղորդագրությունների համար։ useFormStatus-ը թույլ է տալիս ձևի ներսի submit կոճակին իմանալ, որ այն pending է, իսկ useOptimistic-ը սպասվող արդյունքը ցույց է տալիս անմիջապես, քանի դեռ հարցումն ընթանում է։ Նոր use API-ն render-ի ընթացքում կարդում է promise կամ context. promise-ի դեպքում կոմպոնենտը suspend է լինում մինչև այն resolve լինի, և ամենամոտ Suspense boundary-ն ցույց է տալիս fallback։ Այլ հարմարավետ փոփոխություններ. ref-ն այժմ սովորական prop է (առանց forwardRef-ի), և <title> ու <meta> tag-երը կարելի է render անել ցանկացած կոմպոնենտից։ React 19.2-ը ավելացրեց <Activity>՝ UI-ի մասերը թաքցնելու համար՝ պահպանելով դրանց state-ը։
"use client";
import { useActionState } from "react";
type State = { error?: string; ok?: boolean };
async function subscribe(_prev: State, formData: FormData): Promise<State> {
const email = String(formData.get("email") ?? "");
if (!email.includes("@")) return { error: "Please enter a valid email." };
const res = await fetch("/api/subscribe", { method: "POST", body: formData });
return res.ok ? { ok: true } : { error: "Something went wrong." };
}
export function SubscribeForm() {
const [state, action, isPending] = useActionState(subscribe, {});
return (
<form action={action}>
<label htmlFor="email">Email</label> <input id="email" name="email" type="email" required />
<button disabled={isPending}>{isPending ? "Sending..." : "Subscribe"}</button>
{state.error && <p role="alert">{state.error}</p>}
{state.ok && <p role="status">Thanks for subscribing.</p>}
</form>
);
}
06Server Component-ներ
React Server Component-ները (RSC) կոմպոնենտներ են, որոնք աշխատում են միայն սերվերում՝ կամ build-ի ժամանակ, կամ յուրաքանչյուր հարցման համար։ Դրանք կարող են լինել async, անմիջապես կարդալ տվյալների բազայից կամ API-ից և ապահով օգտագործել գաղտնիքներ, իսկ դրանց կոդը երբեք չի ուղարկվում բրաուզերին։ Client-ին stream է արվում միայն render արված արդյունքը։ Client Component-ները, որոնք նշվում են ֆայլի վերևում "use client"-ով, ծանոթ տեսակն են. դրանք JavaScript են ուղարկում, hydrate են լինում բրաուզերում և կարող են օգտագործել state, effect-ներ և event handler-ներ։ Մտավոր մոդելը ծառ է, որտեղ արտաքին մասերը render են լինում սերվերում, իսկ ինտերակտիվ «կղզիները» client կոմպոնենտներ են։ Server կոմպոնենտները կարող են serialisable props փոխանցել client կոմպոնենտներին և նույնիսկ render անել դրանք որպես children։ Server Function-ները, որոնք նշվում են "use server"-ով, թույլ են տալիս client կոդին կանչել server-side կոդը ֆունկցիայի պես, սովորաբար ձևերի submit-ի համար։ RSC-ին պետք է framework, որն աջակցում է այն, ամենից հաճախ՝ Next.js App Router-ը։
// app/products/page.tsx (Server Component: no "use client")
import { AddToCartButton } from "./add-to-cart-button";
export default async function ProductsPage() {
const res = await fetch("https://api.example.com/products");
const products: { id: string; name: string }[] = await res.json();
return (
<ul>
{products.map((p) => (
<li key={p.id}>
{p.name} <AddToCartButton id={p.id} />
</li>
))}
</ul>
);
}
// add-to-cart-button.tsx starts with "use client" and uses useState/onClick
Օգտագործեք, երբ
- Ինտերակտիվ ինտերֆեյսներ՝ շատ state-ով. dashboard-ներ, ադմին պանելներ, խանութներ, էդիտորներ։
- Թիմեր, որոնք ուզում են մեկ կոմպոնենտային մոդել վեբում (React, Next.js), desktop-ում (Electron) և մոբայլում (React Native)։
- Design system-ներ և վերաօգտագործվող UI փաթեթներ, որոնք կիսվում են մի քանի պրոդուկտների միջև։
- Կոնտենտային կայքեր, որոնց պետք է որոշ ինտերակտիվություն, երբ զուգակցվում են server-rendering framework-ի հետ, ինչպես Next.js-ը։
Մի՛ օգտագործեք, երբ
- Հիմնականում ստատիկ էջ կամ պարզ brochure կայք՝ մեկ-երկու ինտերակցիայով, որտեղ մաքուր HTML-ը, CSS-ը և մի քիչ JavaScript-ը ավելի թեթև են։
- Միայն client-side React այն էջերի համար, որոնք պետք է դասակարգվեն որոնողական համակարգերում և արագ բեռնվեն դանդաղ հեռախոսներում. փոխարենը օգտագործիր server rendering (Next.js)։
Այլընտրանքներ
- Vue (with Nuxt)
Template-ի վրա հիմնված կոմպոնենտներ՝ ներկառուցված reactivity-ով. մեղմ ուսուցման կոր և ուժեղ պաշտոնական էկոհամակարգ։
- Svelte (with SvelteKit)
Կոմպիլյատոր, որն արտադրում է շատ փոքր bundle-ներ և state-ի համար ավելի քիչ boilerplate է պահանջում։
- Angular
Ամբողջական, «կարծիք ունեցող» framework՝ dependency injection-ով և signal-ներով, տարածված մեծ ընկերություններում։
- Solid, Preact
React-ի նման API-ներ՝ fine-grained reactivity-ով կամ շատ փոքր չափով, արտադրողականության համար կրիտիկական widget-ների համար։
Լավագույն պրակտիկաներ
- Կոմպոնենտները պահիր փոքր և կենտրոնացված. state-ը բարձրացրու միայն այնքան, որքան անհրաժեշտ է։
- Արժեքները հաշվարկիր render-ի ընթացքում՝ effect-ներով state-ի մեջ սինխրոնացնելու փոխարեն։
- Որպես key օգտագործիր կայուն id-ներ, երբեք array-ի index՝ փոփոխվող ցուցակների համար։
- Server state-ը (TanStack Query, Server Component-ներ) առանձնացրու client state-ից (useState, Zustand)։
- Այն framework-ներում, որոնք աջակցում են, լռելյայն օգտագործիր Server Component-ներ. "use client" ավելացրու միայն այնտեղ, որտեղ ինտերակտիվություն է պետք։
- Օպտիմիզացնելուց առաջ չափիր React DevTools Profiler-ով. ամենուր memo ավելացնելուց առաջ դիտարկիր React Compiler-ը։
- Երբեք մի պահիր զգայուն տվյալներ, օրինակ՝ քարտի raw տվյալներ, persist արվող client store-երում։
Տարածված սխալներ
- useEffect-ով props-ը state-ի մեջ պատճենելը կամ derived արժեքներ հաշվարկելը, ինչը առաջացնում է ավելորդ render-ներ և հնացած տվյալներ։
- useEffect-ում բացակայող կամ սխալ dependency-ներ, որոնք հանգեցնում են հնացած արժեքների կամ անվերջ ցիկլերի։
- State-ի օբյեկտները կամ array-ները անմիջապես mutate անելը, ինչի պատճառով React-ը չի տեսնում փոփոխությունը։
- Արագ փոխվող արժեքները մեծ Context-ի մեջ դնելը, որը հավելվածի կեսն է օգտագործում։
- Effect-ներում տվյալներ fetch անելը՝ առանց loading-ի, սխալների, race condition-ների և caching-ի մշակման։
Խորը վերլուծություն
07Fiber, lane-եր և concurrent rendering
Ներսում React-ը կոմպոնենտների ծառը ներկայացնում է fiber node-երի կապակցված կառուցվածքով՝ մեկ node յուրաքանչյուր կոմպոնենտի կամ էլեմենտի համար, որը պահում է իր props-ը, state-ը, hook-երի ցուցակը և սպասվող աշխատանքը։ Քանի որ rendering-ը բաժանված է այս fiber-ների վրա աշխատանքի փոքր միավորների, React-ը կարող է դադարեցնել render-ը, մշակել ավելի հրատապ բան և շարունակել, կամ դեն նետել չավարտված աշխատանքը։ Յուրաքանչյուր թարմացման տրվում է առաջնահերթության lane. ստեղնի սեղմումը կամ click-ը հրատապ է, իսկ startTransition-ով փաթաթված թարմացումը transition է, որը կարող է ընդհատվել։ Սա concurrent հնարավորությունների հիմքն է։ useTransition-ը state-ի թարմացումը նշում է որպես ոչ հրատապ և քեզ isPending flag է տալիս. React 19-ում callback-ը կարող է async լինել։ useDeferredValue-ն քեզ տալիս է արժեքի «հետ մնացող» պատճենը, այնպես որ ծանր ցուցակը render է լինում հին արժեքով, իսկ input-ը մնում է արձագանքող։ Commit փուլը, որտեղ React-ը փոփոխությունները կիրառում է DOM-ի վրա, երբեք չի ընդհատվում, այնպես որ օգտատերերը երբեք կիսով չափ թարմացված էկրան չեն տեսնում։ Հենց սրա համար էլ render ֆունկցիաները պետք է pure լինեն. React-ը կարող է դրանք կանչել մեկից ավելի անգամ կամ ընդհանրապես commit չանել դրանց արդյունքը։
import { useDeferredValue, useState, useTransition, memo } from "react";
function ProductSearch({ products }: { products: { id: string; name: string }[] }) {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query); // lags behind while busy
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState<"all" | "sale">("all");
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<button onClick={() => startTransition(() => setTab("sale"))}>Sale</button>
{isPending && <span>Loading...</span>}
<Results products={products} query={deferredQuery} tab={tab} />
</>
);
}
const Results = memo(function Results(props: { products: { id: string; name: string }[]; query: string; tab: string }) {
return <ul>{props.products.filter((p) => p.name.includes(props.query)).map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
});
08Hydration, mismatch-եր և Suspense boundary-ներ
Երբ էջը server-rendered է, բրաուզերը նախ ստանում է պարզ HTML։ Hydration-ը React-ի կողմից event handler-ների և state-ի կցումն է այդ առկա HTML-ին՝ client-ի վրա նույն ծառը render անելով և այն node առ node համեմատելով։ Եթե client-ի render-ը տարբեր output է տալիս, օրինակ՝ որովհետև օգտագործել է Date.now(), Math.random(), պատուհանի չափը, օգտատիրոջ locale-ը կամ localStorage, React-ը հաղորդում է hydration mismatch և կարող է այդ մասը զրոյից re-render անել՝ կորցնելով server rendering-ի օգուտը։ Ուղղիր՝ երկու կողմերում նույնական output ստանալով. արժեքը փոխանցիր սերվերից կամ միայն client-ի մասը render արա mount-ից հետո, իսկ suppressHydrationWarning-ն օգտագործիր միայն անխուսափելի մեկ տեքստային տարբերությունների համար, օրինակ՝ timestamp-ի։ Suspense boundary-ները որոշում են, թե որտեղ են հայտնվում loading վիճակները, իսկ streaming SSR-ի դեպքում դրանք նաև hydration-ի միավորներ են. յուրաքանչյուր boundary կարող է իր HTML-ը stream անել և hydrate լինել անկախ, և React-ը առաջնահերթություն է տալիս այն մասի hydration-ին, որի հետ օգտատերը փոխազդում է։ Boundary-ները տեղադրիր անկախ, դանդաղ հատվածների շուրջ, ոչ թե յուրաքանչյուր կոմպոնենտի, այլապես էջը կթարթի spinner-ներով։
import { Suspense, useSyncExternalStore } from "react";
// Same output on server and first client render, then real value
function useIsClient() {
return useSyncExternalStore(
() => () => {},
() => true, // client snapshot
() => false, // server snapshot
);
}
function LocalTime({ iso }: { iso: string }) {
const isClient = useIsClient();
return <time dateTime={iso}>{isClient ? new Date(iso).toLocaleString() : iso.slice(0, 10)}</time>;
}
function Page() {
return <main><ProductDetails /><Suspense fallback={<p>Loading reviews...</p>}><Reviews /></Suspense></main>;
}
09React Compiler-ը և կանոնները, որոնց վրա այն հենվում է
React Compiler-ը (1.0՝ 2025-ի հոկտեմբերից) build-time գործիք է, որը վերլուծում է քո կոմպոնենտներն ու hook-երը և ավտոմատ ավելացնում memoisation՝ ավելի մանր մակարդակով, քան մարդկանց մեծ մասը ձեռքով կգրեր. առանձին JSX էլեմենտներ և արժեքներ, ոչ միայն ամբողջական կոմպոնենտներ։ Այն կարող է դա ապահով անել միայն այն դեպքում, եթե քո կոդը հետևում է Rules of React-ին. կոմպոնենտներն ու hook-երը pure են render-ի ընթացքում, props-ը և state-ը դիտարկվում են որպես immutable, hook-երը կանչվում են անպայմանորեն վերին մակարդակում, և hook-երին փոխանցված արժեքները հետո չեն փոփոխվում։ Այս կանոնները խախտող կոդը բաց է թողնվում, ոչ թե սխալ կոմպիլյացվում, այնպես որ հավելվածը դեռ աշխատում է, բայց այդ կոմպոնենտները ոչինչ չեն շահում։ eslint-plugin-react-hooks-ի recommended կանոնները հաղորդում են նույն խախտումները, ուստի միացրու դրանք կոմպիլյատորը միացնելուց առաջ։ Ներդրումը սովորաբար աստիճանական է. միացրու այն մեկ պանակի համար, չափիր Profiler-ով, հետո ընդլայնիր։ Միացնելուց հետո ձեռքով գրված useMemo, useCallback և memo կանչերի մեծ մասն ավելորդ է դառնում, թեև առկաները դեռ հարգվում են։
// Breaks the rules: mutating during render, so the compiler skips this component
function CartTotal({ items }: { items: { price: number; qty: number }[] }) {
items.sort((a, b) => b.price - a.price); // mutates a prop!
return <p>{items.reduce((sum, i) => sum + i.price * i.qty, 0)}</p>;
}
// Follows the rules: derive a new array, no manual memo needed with the compiler
function CartTotalFixed({ items }: { items: { price: number; qty: number }[] }) {
const sorted = items.toSorted((a, b) => b.price - a.price);
const total = sorted.reduce((sum, i) => sum + i.price * i.qty, 0);
return <p>{total}</p>;
}
// next.config.ts: { reactCompiler: true }
// Vite: babel-plugin-react-compiler in the React plugin's babel options
10Rendering-ի արագությունը մասշտաբով՝ ցուցակներ, context և code splitting
React-ի արագության խնդիրների մեծ մասը մի քանի pattern-ի մեջ է տեղավորվում։ Շատ երկար ցուցակներ. 5,000 տողի render-ը ստեղծում է 5,000 DOM node՝ անկախ նրանից, թե որքան արագ է React-ը, ուստի virtualise արա TanStack Virtual-ի նման գրադարանով, որը render է անում միայն տեսանելի տողերը, կամ էջավորիր։ Context fan-out. հաճախ փոխվող օբյեկտ պահող մեկ context-ը re-render է անում բոլոր consumer-ներին. բաժանիր այն առանձին context-ների state-ի և action-ների համար կամ արագ փոխվող տվյալները տեղափոխիր selector-ներով store։ Չափից բարձր դրված state. էջի վերևում դաշտում տպելը re-render է անում ներքևի ամեն ինչ. state-ը տեղափոխիր ներքև՝ այն կոմպոնենտի մեջ, որին այն պատկանում է, կամ ծանր ենթածառերը փոխանցիր որպես children, որպեսզի դրանք re-render չլինեն։ Մեծ bundle-ներ. հազվադեպ օգտագործվող էկրանները բաժանիր lazy()-ով և Suspense-ով, իսկ chart-երի կամ էդիտորների նման ծանր գրադարանները import արա միայն այնտեղ, որտեղ պետք են։ Միշտ հաստատիր React DevTools Profiler-ով՝ միացնելով «Highlight updates when components render»-ը, և չափիր throttle արված միջին կարգի հեռախոսի վրա, ոչ թե ծրագրավորողի laptop-ի։
import { lazy, Suspense, useState } from "react";
// Code-split a heavy screen: loaded only when first rendered
const ReportsChart = lazy(() => import("./reports-chart"));
export function Dashboard() {
const [showReports, setShowReports] = useState(false);
return (
<section>
<button onClick={() => setShowReports(true)}>Show reports</button>
{showReports && (
<Suspense fallback={<p>Loading chart...</p>}>
<ReportsChart />
</Suspense>
)}
</section>
);
}
Իրական իրավիճակներ
Ֆիլտրի դաշտում տպելը սառեցնում է ապրանքների ցուցակը
Խնդիր
Admin էջը ցույց է տալիս 3,000 ապրանք՝ վերևում որոնման դաշտով։ Միջին կարգի laptop-ի վրա յուրաքանչյուր ստեղնի սեղմում հայտնվելու համար 300 ms է պահանջում, և աշխատակիցները բողոքում են, որ input-ը հետ է մնում տպելուց։
Ինչպես լուծել
Նախ profile արա React DevTools Profiler-ով՝ հաստատելու, որ ծախսը ցուցակն է։ Հետո առանձնացրու հրատապ և ոչ հրատապ աշխատանքը. input-ը պահիր ուղղակիորեն կապված state-ին, իսկ ցուցակին փոխանցիր useDeferredValue(query)՝ memo-ով փաթաթված կամ React Compiler-ով կոմպիլյացված, այնպես որ տպելը մնում է ակնթարթային, իսկ ցուցակը հետո է հասնում։ Ցուցակը virtualise արա TanStack Virtual-ով, որպեսզի DOM-ում լինի ընդամենը մոտ 30 տող։ Փոքրատառ որոնման դաշտը նախապես հաշվարկիր մեկ անգամ, ոչ թե ամեն ստեղնի սեղմման ժամանակ։ Եթե տվյալները շարունակեն աճել, ֆիլտրումը տեղափոխիր սերվեր՝ debounce արված request-ով, և չեղարկիր հնացածները։ Նորից չափիր. input-ը պետք է արձագանքի մեկ frame-ի ընթացքում։
Hydration սխալներ՝ օգտատիրոջ արժույթով գներ ավելացնելուց հետո
Խնդիր
Գներն ու ամսաթվերը բրաուզերի locale-ով ֆորմատավորելուց հետո console-ը լցվում է hydration mismatch նախազգուշացումներով, և որոշ ապրանքների card-եր բեռնվելիս թարթում և re-render են լինում։
Ինչպես լուծել
Սերվերը render է անում իր locale-ով և ժամային գոտով, բրաուզերը՝ օգտատիրոջ, ուստի HTML-ը տարբերվում է։ Locale-ը և արժույթը որոշիր սերվերում՝ URL-ից, cookie-ից կամ Accept-Language header-ից, և փոխանցիր ներքև, որպեսզի երկու կողմերն էլ նույնական ֆորմատավորեն Intl.NumberFormat-ով։ Այն արժեքների համար, որոնք իսկապես կախված են սարքից, օրինակ՝ տեղական ժամը, սերվերում render արա կայուն placeholder, իսկ ճշգրիտ արժեքը՝ mount-ից հետո՝ useSyncExternalStore-ով client ստուգման կամ effect-ի միջոցով։ suppressHydrationWarning-ն օգտագործիր միայն մեկ տեքստային node-ի վրա՝ անխուսափելի timestamp-ի համար։ Ավելացրու թեստ, որը render է անում սերվերում և hydrate է անում jsdom-ում՝ ձախողվելով mismatch նախազգուշացումների դեպքում։
Ընդհանուր React package-ը բագեր է առաջացնում տասը client կայքերում
Խնդիր
Ներքին UI package-ն օգտագործվում է բազմաթիվ կայքերի կողմից։ Minor release-ը փոխեց կոմպոնենտի լռելյայն վարքագիծը և կոտրեց checkout-ը դրանցից մի քանիսում, և ոչ ոք չնկատեց, մինչև հաճախորդները չբողոքեցին։
Ինչպես լուծել
Package-ին վերաբերվիր որպես public գրադարանի։ Խստորեն հետևիր semantic versioning-ին. վարքագծի փոփոխությունները major են՝ changelog-ով և միգրացիայի նշումներով։ React-ը պահիր որպես peer dependency, որպեսզի կայքերը երբեք երկու պատճեն չբեռնեն։ Յուրաքանչյուր export արված կոմպոնենտի համար ավելացրու visual regression և interaction թեստեր, գումարած contract test, որը կոմպոնենտները render է անում յուրաքանչյուր սպառող կայքի տիպիկ props-ով։ Հրապարակիր pre-release տարբերակներ և թեստավորիր դրանք երկու-երեք ներկայացուցչական կայքում stable release-ից առաջ։ Վարքագծի փոփոխությունների համար օգտագործիր feature flag-եր կամ նոր props՝ լռելյայն արժեքները փոխելու փոխարեն։ Վերջապես, գրանցիր, թե որ կայքերն են որ տարբերակն օգտագործում, որպեսզի ուղղումը կարելի լինի տարածել վերահսկվող հերթականությամբ։
Արագ ուղեցույց
- Թարմացումը նշել որպես ոչ հրատապ
const [isPending, startTransition] = useTransition();- Input-ը արագ պահել, ծանր ցուցակին թույլ տալ հետ մնալ
const deferred = useDeferredValue(query);- Կոմպոնենտի code splitting
const Chart = lazy(() => import("./chart"));- Վերակայել կոմպոնենտի state-ը, երբ id-ն փոխվում է
<Profile key={userId} userId={userId} />- Ապահով բաժանորդագրվել բրաուզերի API-ին
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)- Կարդալ վերջին արժեքները effect-ում՝ առանց այն վերագործարկելու
const onTick = useEffectEvent(() => log(count));- Optimistic UI mutation-ի ընթացքում
const [optimistic, addOptimistic] = useOptimistic(items, reducer);- Միացնել React Compiler-ը Next.js-ում
// next.config.ts { reactCompiler: true }- Lint անել Rules of React-ը
npm i -D eslint-plugin-react-hooks // use its recommended config- Գտնել դանդաղ render-ները
React DevTools > Profiler > record > sort by render duration
Իմ իրական նախագծերում
- Ներքին React փաթեթների էկոհամակարգի հիմնական contributor-ներից եմ (մոտ 1000 իմ commit-ներ)՝ config-driven ադմին համակարգով, մոտ 20 կոմպոնենտից բաղկացած design system-ով և account, cart ու form փաթեթներով, որոնք օգտագործվում են մոտ 10 հաճախորդների կայքերում։
- Այդ էկոհամակարգում memoise արեցի provider-ները, loading counter-ը դարձրի ապահով server rendering-ի համար և քարտի raw տվյալները հեռու պահեցի persist արվող cart store-ից՝ ստուգելով, որ server-rendered արդյունքը մնաց նույնը։
- Multi-tenant POS SaaS-ն օգտագործում է React 19 Vite-ով, TanStack Query՝ server state-ի համար և Zustand՝ client state-ի համար։
- Իմ հինգ React Native հավելվածները կիսում են մեկ template. սուրճ պատվիրելու հավելվածում էկրանները կարդում են միայն store-երից, ինչը տվյալների տրամաբանությունը հեռու է պահում UI-ից։
Հարցեր և պատասխաններ
Q1Ո՞րն է state-ի և props-ի տարբերությունը։
Props-ը մուտքեր են, որոնք ծնողից փոխանցվում են երեխա կոմպոնենտին։ Երեխան կարդում է դրանք, բայց չպետք է փոխի, ինչպես ֆունկցիայի արգումենտները։ State-ը տվյալներ են, որոնք կոմպոնենտը տիրապետում է և կարող է ժամանակի ընթացքում թարմացնել, օրինակ՝ ձևի մուտքագրումը, բաց կամ փակ մենյուն կամ ընտրված tab-ը։ Երբ state-ը փոխվում է, React-ը re-render է անում կոմպոնենտը. երբ ծնողը re-render է լինում նոր props-ով, երեխան նույնպես re-render է լինում։ Լավ կանոն. եթե արժեքը կարելի է հաշվարկել props-ից կամ այլ state-ից, այն ընդհանրապես չպետք է state լինի։ Եթե երկու sibling-ի պետք է նույն state-ը, տեղափոխիր այն նրանց ընդհանուր ծնողի մոտ և փոխանցիր ներքև որպես props։
Q2Ինչո՞ւ են key-երը կարևոր ցուցակներ render անելիս։
Key-երը թույլ են տալիս React-ին reconciliation-ի ժամանակ համապատասխանեցնել ցուցակի տարրերը render-ների միջև։ Կայուն key-երով, սովորաբար տվյալների բազայի id-ներով, React-ը գիտի, որ 42 id-ով տարրը երրորդ դիրքից տեղափոխվել է առաջին և կարող է պահպանել դրա DOM node-ը և ներքին state-ը։ Array-ի index-ները որպես key օգտագործելիս տարր ավելացնելը կամ հեռացնելը տեղաշարժում է բոլոր index-ները, և React-ը կարող է վերաօգտագործել սխալ կոմպոնենտը. input-ը պահում է այլ տողի տեքստ, կամ անիմացիաներն ու focus-ը ցատկոտում են։ Key-երը պետք է եզակի լինեն sibling-ների միջև և կայուն՝ render-ների միջև. երբեք դրանք պատահականորեն մի գեներացրու render-ի ընթացքում, որովհետև դա ստիպում է React-ին ամեն անգամ վերստեղծել յուրաքանչյուր տարր։
Q3Ե՞րբ պետք է օգտագործել useEffect, և ե՞րբ՝ ոչ։
useEffect-ը React-ից դուրս համակարգերի հետ սինխրոնացման համար է՝ subscription-ներ, թայմերներ, WebSocket-ներ, բրաուզերի API-ներ, երրորդ կողմի widget-ներ կամ անալիտիկա։ Այն աշխատում է commit-ից հետո, իսկ դրա cleanup-ը՝ հաջորդ effect-ից առաջ և unmount-ի ժամանակ։ Սովորաբար այն պետք չէ derived տվյալների համար (հաշվարկիր render-ի ընթացքում, useMemo-ով, եթե թանկ է), օգտատիրոջ event-ների մշակման համար (տրամաբանությունը դիր event handler-ում) կամ prop-ի փոփոխության ժամանակ state-ը reset անելու համար (օգտագործիր key)։ Effect-ների ներսում տվյալների fetch-ը աշխատում է, բայց պահանջում է race condition-ների մշակում և caching, ուստի նախընտրում եմ TanStack Query, SWR կամ Server Component-ներ։ Ավելի քիչ effect-ներ՝ ավելի քիչ բագեր։
Q4Ինչպե՞ս ես ընտրում Context-ի, Zustand-ի և TanStack Query-ի միջև։
Ըստ state-ի տեսակի։ TanStack Query-ն (կամ SWR-ը) server state-ի համար է. API-ին պատկանող տվյալներ, որոնց պետք է caching, background refetch, deduplication և invalidation՝ mutation-ներից հետո։ Zustand-ը հաճախ փոխվող գլոբալ client state-ի համար է, ինչպես cart-ը, ֆիլտրերը կամ բազմաքայլ հոսքը. կոմպոնենտները subscribe են անում հատվածներին, ուստի re-render է լինում միայն այն, ինչ փոխվել է։ Context-ը լավագույնն է dependency injection-ի և հազվադեպ փոխվող արժեքների համար, ինչպես theme-ը, locale-ը կամ ընթացիկ օգտատերը. այն յուրաքանչյուր փոփոխության ժամանակ re-render է անում բոլոր consumer-ներին, ուստի վատ store է արագ փոխվող տվյալների համար։ Լոկալ useState-ը դեռ լռելյայնն է այն ամենի համար, ինչ պետք է միայն մեկ կոմպոնենտի։
Q5Ի՞նչ են React Server Component-ները, և ինչո՞վ են տարբերվում SSR-ից։
Server-side rendering-ը (SSR) client կոմպոնենտները սերվերում render է անում HTML-ի՝ արագ առաջին paint-ի համար, բայց դրանց ամբողջ JavaScript-ը դեռ ուղարկվում և hydrate է լինում բրաուզերում։ Server Component-ները կոմպոնենտների այլ տեսակ են. դրանք աշխատում են միայն սերվերում, կարող են լինել async և անմիջապես դիմել տվյալների բազաներին կամ գաղտնիքներին, իսկ դրանց կոդը երբեք չի հասնում client-ին։ Ներքև stream է արվում միայն դրանց render արված արդյունքը, իսկ ինտերակտիվ մասերը Client Component-ներ են՝ նշված "use client"-ով։ Երկուսն աշխատում են միասին. Next.js էջը render է անում Server Component-ները, Client Component-ները server-render է անում HTML-ի, հետո hydrate է անում միայն client մասերը։ Արդյունքը ավելի քիչ JavaScript և ավելի պարզ data fetching է։
Q6Ի՞նչ փոխվեց React 19-ում, որը կարևոր է ամենօրյա աշխատանքում։
Ամենամեծ փոփոխությունը Action-ներն են. async ֆունկցիաներ, որոնք React-ը աշխատեցնում է transition-ում, սովորաբար ձևի action prop-ի միջոցով։ useActionState-ի, useFormStatus-ի և useOptimistic-ի հետ միասին դրանք հեռացնում են ձևերում ձեռքով գրված loading, error և optimistic-update կոդի մեծ մասը։ use API-ն render-ի ընթացքում կարդում է promise-ներ և context՝ աշխատելով Suspense-ի հետ։ ref-ն այժմ սովորական prop է, ուստի forwardRef-ն այլևս պետք չէ, և կոմպոնենտները կարող են անմիջապես render անել <title> և <meta> tag-եր։ Server Component-ները և Server Function-ները stable են framework-ների համար։ React 19.2-ը ավելացրեց <Activity> և useEffectEvent, իսկ React Compiler-ը հասավ 1.0-ի՝ ավտոմատացնելով memoisation-ի մեծ մասը, որը մարդիկ նախկինում գրում էին ձեռքով։
Սենիոր մակարդակի հարցեր
S1Ինչպե՞ս կնախագծես state management-ը բազմաթիվ թիմերով մեծ React հավելվածի համար։
Նախ դասակարգում եմ state-ը, որովհետև խնդիրների մեծ մասը գալիս է տեսակները խառնելուց։ Server state-ը անցնում է մեկ տվյալային շերտով՝ TanStack Query կամ Server Components, համաձայնեցված query-key պայմանավորվածություններով և invalidation-ի կանոններով, այնպես որ caching-ը հետևողական է, և ոչ ոք API տվյալները չի պատճենում store-երում։ URL state-ը՝ ֆիլտրեր, էջավորում և tab-եր, ապրում է router-ում, որպեսզի կարելի լինի կիսվել։ Գլոբալ client state-ը փոքր է պահվում, բաժանված feature-ներին պատկանող Zustand store-երի՝ selector-ներով, և յուրաքանչյուր store ունի հստակ տեր թիմ։ Մնացած ամեն ինչ մնում է լոկալ։ Feature-ների միջև հաղորդակցությունն անցնում է բացահայտ interface-ների, event-ների կամ ընդհանուր hook-երի միջոցով, ոչ թե ուրիշ թիմի store-ի մեջ ձեռք մտցնելով։ Փոխզիջումը ավելի շատ կառուցվածքն է և feature-ների միջև որոշ կրկնություն, որն ընդունվում է՝ փոխարենը թիմերը կարողանում են փոխել իրենց հատվածը՝ առանց ուրիշներին կոտրելու։
S2Ի՞նչ խնդիրներ են լուծում Server Components-ը, և ի՞նչն են դրանք ավելի բարդ դարձնում։
Դրանք լուծում են երեք իրական խնդիր։ Bundle-ի չափ. կոդը, որը միայն տվյալներ է ֆորմատավորում, markdown render անում կամ տվյալների բազա կանչում, մնում է սերվերում, այնպես որ բրաուզերները ավելի քիչ JavaScript են ներբեռնում և parse անում։ Տվյալների ստացում. կոմպոնենտները կարող են ուղղակիորեն await անել տվյալները՝ օգտագործման վայրին մոտ, առանց client waterfall-ների կամ միայն UI-ի համար կառուցված API շերտի։ Անվտանգություն. գաղտնիքները և ներքին ծառայությունները երբեք չեն հասնում client-ին։ Ավելի բարդ է դառնում մտավոր մոդելը. ծրագրավորողները պետք է իմանան, թե ամեն ֆայլ որ կողմում է աշխատում, ինչը կարող է անցնել սահմանը (միայն serialisable props) և ինչպես են աշխատում caching-ը և revalidation-ը իրենց framework-ում։ Debugging-ը ընդգրկում է երկու միջավայր, իսկ context կամ effect-ներ օգտագործող երրորդ կողմի գրադարաններին client wrapper-ներ են պետք։ Դրանք նաև քեզ կապում են այն framework-ին, որն իրականացնում է դրանք։ Login-ի հետևում բարձր ինտերակտիվ հավելվածների համար client-side SPA-ն դեռ կարող է ավելի պարզ ընտրություն լինել։
S3Տվյալների ստացում useEffect-ով, TanStack Query, թե Server Components. ինչպե՞ս ես ընտրում։
useEffect-ում «հում» fetch-ը ամենաթույլ լռելյայն տարբերակն է. race condition-ները, չեղարկումը, caching-ը, deduplication-ը, retry-ները և refetch-ը պետք է ինքդ մշակես, և այն request waterfall-ներ է ստեղծում, ուստի օգտագործում եմ միայն պարզ, մեկուսացված դեպքերում։ TanStack Query-ն ճիշտ գործիքն է client-rendered հավելվածների և բարձր ինտերակտիվ տվյալների համար՝ dashboard-ներ, admin աղյուսակներ, անվերջ ցուցակներ, optimistic mutation-ներ և ֆոնային թարմացում, երբ tab-ը նորից focus է ստանում։ Server Components-ը հարմար է նախնական էջի render-ի համար անհրաժեշտ տվյալներին, հատկապես public, SEO-ի համար կարևոր կամ գաղտնիքներից կախված տվյալներին, որովհետև դրանք գալիս են HTML-ի հետ և client JavaScript չեն ավելացնում։ Next.js հավելվածում սովորաբար համադրում եմ դրանք. Server Components առաջին render-ի համար և Server Actions mutation-ների համար, իսկ TanStack Query-ն պահում եմ այն մասերի համար, որոնց պետք են live client-side թարմացումներ, polling կամ բարդ caching։
Բառարան
- Fiber
- React-ի ներքին աշխատանքի միավոր. մեկ node յուրաքանչյուր կոմպոնենտի կամ էլեմենտի համար, որը պահում է props, state և սպասվող թարմացումներ։
- Reconciliation
- Նոր էլեմենտների ծառի համեմատումը նախորդի հետ՝ DOM-ի նվազագույն փոփոխությունները որոշելու համար։
- Commit phase
- Անընդհատելի քայլ, որտեղ React-ը հաշվարկված փոփոխությունները կիրառում է DOM-ի վրա և աշխատեցնում layout effect-ները։
- Transition
- State-ի ոչ հրատապ թարմացում, որը React-ը կարող է ընդհատել, որպեսզի հրատապ ինտերակցիաները, օրինակ՝ տպելը, արձագանքող մնան։
- Hydration
- React-ի state-ի և event handler-ների կցումը սերվերում render արված HTML-ին։
- Hydration mismatch
- Client-ի առաջին render-ը տարբերվում է սերվերի HTML-ից՝ ստիպելով React-ին re-render անել այդ մասը։
- Suspense boundary
- Կոմպոնենտ, որը fallback է ցույց տալիս, մինչ իր children-ը սպասում են կոդի կամ տվյալների, և stream է լինում անկախ։
- Virtualisation
- Միայն էկրանին ներկայումս տեսանելի ցուցակի տողերի render՝ տվյալների բոլոր տողերի փոխարեն։
- Rules of React
- Purity-ի, immutability-ի և hook-երի հերթականության կանոններ, որոնք ապահով են դարձնում React-ի օպտիմալացումները և կոմպիլյատորը։
- Peer dependency
- Package, որը գրադարանը սպասում է, որ host հավելվածը կտրամադրի, օրինակ՝ React-ը, որպեսզի բեռնվի միայն մեկ պատճեն։
Շարունակեք սովորել
Պրոդուկտի գաղափար ունե՞ք։Եկեք ստեղծենք այն։
Պատմեք ձեր գաղափարի, պրոդուկտի կամ թիմի մասին։ Սովորաբար պատասխանում եմ մեկ աշխատանքային օրվա ընթացքում։