32-წუთიანი სამუშაო სესია იმ ტიპების სისტემაზე, რომელიც თანამედროვე JavaScript-ის უკან დგას — იმით დაწყებული, თუ რატომ ამართლებს ტიპები, იუნიონებით, ჯენერიკებითა და utility ტიპებით გავლით, მკაცრ რეჟიმამდე და იმ ადგილებამდე, სადაც ტიპები ჩუმად ცრუობს.
JavaScript მხოლოდ მაშინ გეუბნებათ, რომ რაღაც არ არის რიგზე, როცა მომხმარებელი მას პროდაქშენში წააწყდება. TypeScript ამ უკუკავშირს წერის მომენტში გადმოაქვს — კითხულობს თქვენს კოდს, ამოწმებს ყოველ მნიშვნელობას იმ ფორმასთან, რომელიც დაჰპირდით, და შეუსაბამობას რედაქტორშივე გაჩვენებთ. იგივე ჩანაწერი ამავე დროს ყოველთვის ზუსტ დოკუმენტაციადაც მუშაობს.
.ts ფაილებს და ამტკიცებს, რომ ყოველი მნიშვნელობა თავის გამოცხადებულ ფორმას ემთხვევა. შეუსაბამობები ის შეცდომებია, რომლებსაც რედაქტირებისასვე ხედავთ..js-ად იქცევა, რომელსაც ბრაუზერები და Node უშვებენ.შემმოწმებელი თქვენს ტიპებს წინასწარ ამტკიცებს; გამოცემულ JavaScript-ს მათგან არაფერი მიჰყვება. დაიმახსოვრეთ ეს სხვაობა — ნაწილი 7 სწორედ იმაზეა, სად გტკენთ ის.
აკრეფის შეცდომა რანტაიმში. user.naem შეცდომაა თქვენს რედაქტორში და არა კრახი პროდაქშენში.
უშიშარი რეფაქტორინგი. გადაარქვით ველს სახელი და შემმოწმებელი ჩამოგითვლით ყველა ადგილს, სადაც განახლებაა საჭირო.
ცოცხალი დოკუმენტაცია. ტიპის ხელმოწერა ხსნის, რას ითხოვს და რას აბრუნებს ფუნქცია — და ვერასდროს მოძველდება.
ნამდვილი ავტოშევსება. რედაქტორმა ფორმა იცის, ამიტომ სწორ ველებს გთავაზობთ და არასწორებს იჭერს.
function total(cart) { return cart.reduce((sum, i) => sum + i.prce, 0) } // typo "prce" → every total becomes NaN // nothing complains until a user checks out
type Item = { price: number } function total(cart: Item[]): number { return cart.reduce((sum, i) => sum + i.prce, 0) } // ✕ Property 'prce' does not exist on 'Item'
TypeScript-ს აქ უკვე ყველგან ხვდებით — მასზე დგას Modern React-ის კომპონენტები და State Management-ის სთორები, და ის ბუნებრივი ენაა OOP & Architecture-ის დიზაინის იდეებისთვის. ეს დეკი სწორედ ის ტიპების სისტემაა, რომელსაც ის დეკები ეყრდნობა.
პირველი გასაკვირი ახალბედისთვის: ტიპები არ ედრება იმის მიხედვით, საიდან მოვიდნენ, არამედ იმის მიხედვით, როგორ გამოიყურებიან. თუ მნიშვნელობას აქვს ის ველები, რომლებიც ფუნქციას სჭირდება, ის ერგება — არც მემკვიდრეობა და არც იარლიყი არაა საჭირო. ეს არის სტრუქტურული ტიპიზაცია, და სწორედ ის აქცევს სისტემას მსუბუქად და JavaScript-ისთვის ბუნებრივად.
ყველაფერი, რასაც x და y აქვს, Point-ია — ზედმეტი ველები არაფერს შველის და არც აფუჭებს; აკლდეს კი არ შეიძლება.
string, number, boolean, null, undefined, პლუს bigint და symbol.?-ით აღინიშნება.number[] სიისთვის, [string, number] ფიქსირებული წყვილისთვის.მიაწერეთ ცვლადს ტიპი :-ით, ან მიეცით TypeScript-ს საშუალება, თავად დაასკვნას ის მნიშვნელობიდან — ჩვეულებრივ სჯობს, თავად დაასკვნას.
let name: string = "Ada" let age = 42 // inferred: number let on: boolean = true
interface და type ორივე ასახელებს ფორმას; აირჩიეთ ერთი და თანმიმდევრული იყავით. ? არასავალდებულო ველს აღნიშნავს.
interface User { id: number name: string bio?: string // optional }
ერთგვაროვანი სია ერთი მხრივ და ფიქსირებული სიგრძის, პოზიციური ტუპლი მეორე მხრივ — თანმიმდევრობასა და რაოდენობას მნიშვნელობა აქვს.
const ids: number[] = [1, 2] const pair: [string, number] = ["a", 1]
პარამეტრები და დასაბრუნებელი ტიპი ქმნის კონტრაქტს; მასთან მოწმდება როგორც გამომძახებლები, ისე თავად სხეული.
function greet( n: string ): string { return `Hi ${n}` }
interface თუ type — რომელს მივმართოთtype-ს მაშინვე მიმართეთ, როგორც კი მარტივი ობიექტის გარდა სხვა რამ დაგჭირდებათ.რეალური მნიშვნელობები ხშირად რამდენიმე ფორმიდან ერთ-ერთია: მოთხოვნა იტვირთება, ან ჩაიტვირთა, ან ჩავარდა. იუნიონი ზუსტად ამას აღწერს. მოგება დავიწროებაა: if-ის ან switch-ის შიგნით TypeScript აკვირდება, რა შეამოწმეთ, და შესაბამისი წევრების უსაფრთხოდ გამოყენების საშუალებას გაძლევთ.
A | B) — მნიშვნელობა, რომელიც რამდენიმე ტიპიდან ერთ-ერთია. ინტერსექცია (A & B) — მნიშვნელობა, რომელიც ერთდროულად ყველა ჩამოთვლილ ტიპს აკმაყოფილებს (მათ ველებს აერთიანებს). დავიწროება ის გზაა, რომლითაც რანტაიმის შემოწმება (მაგალითად typeof) კომპილატორს ეუბნება, ამჟამად იუნიონის რომელი წევრი გიჭირავთ.მცველის გარეთ x მთელი იუნიონია; თითოეული განშტოების შიგნით კი ერთი კონკრეტული ტიპია, ამ ტიპის მეთოდებით.
typeof — პრიმიტივებისთვის: typeof x === "string".instanceof — კლასებისთვის: err instanceof Error.in — ობიექტებისთვის: "radius" in shape ირჩევს იმ წევრს, რომელსაც ეს ველი აქვს.x is Cat-ს აბრუნებს, კომპილატორს თქვენს წესს ასწავლის.მიეცით იუნიონის ყოველ წევრს საერთო ლიტერალური ტეგის ველი. switch ამ ტეგზე იდეალურად ავიწროებს, კომპილატორს კი შეუძლია დაამტკიცოს, რომ ყველა შემთხვევა დაამუშავეთ.
type Shape = | { kind: "circle"; r: number } | { kind: "square"; size: number } function area(s: Shape): number { switch (s.kind) { // the tag case "circle": return Math.PI * s.r ** 2 case "square": return s.size ** 2 } } // add a kind → compiler flags missing case
ერთი ტეგის ველი თითოეულ განშტოებას ზუსტად ერთ ფორმაზე მიმართავს — და სისრულის შემოწმებასაც ის ასაზრდოებს.
never-ითდაამატეთ default, რომელიც მნიშვნელობას never-ს ანიჭებს. თუ მოგვიანებით იუნიონს ახალ წევრს დაამატებთ და მის დამუშავებას დაივიწყებთ, მინიჭება კომპილაციას ვერ გაივლის — უფასო შეხსენება.
ჯენერიკი არის ტიპის პარამეტრი — ადგილის დამჭერი, რომელიც <T>-ით იწერება და რომელსაც გამომძახებელი ავსებს. ის ერთ ფუნქციას ან ტიპს ნებისმიერ ტიპზე მუშაობის საშუალებას აძლევს და მაინც ზუსტად ახსოვს, რომელი გამოიყენეს. ალტერნატივა, any, ასევე "მუშაობს" — მაგრამ ამ ცოდნას გადაყრის.
Array<T> ყოველდღიური მაგალითია: T[] არის "რაღაც ტიპის მასივი", და როგორც კი string[]-ს შექმნით, კომპილატორმა იცის, რომ ყოველი ელემენტი string-ია. აბსტრაქციას ერთხელ წერთ; კონკრეტული ტიპი კი თან მიჰყვება.გადაეცით string[] და შედეგი string-ია — და არა any. გამომძახებლის მიწოდებული ტიპი შესასვლელიდან გამოსასვლელამდე მიედინება.
<T> სახელის შემდეგ პარამეტრს აცხადებს; T უბრალოდ მიღებული ასოა.T-ს იშვიათად გადასცემთ ცალსახად — TypeScript მას ასკვნის თქვენ მიერ მიცემული არგუმენტებიდან.<T extends …> ზღუდავს, რა შეიძლება იყოს T, ასე რომ გარანტირებულ ველებს უსაფრთხოდ იყენებთ.<T = string> ნაგულისხმევს აძლევს, როცა ვერაფერი დაისკვნება.// preserves the element type for every caller function first<T>(arr: T[]): T | undefined { return arr[0] } const n = first([1, 2, 3]) // number | undefined const s = first(["a", "b"]) // string | undefined // a constraint lets you use a guaranteed field function longest<T extends { length: number }>(a: T, b: T) { return a.length >= b.length ? a : b // .length is safe }
შეზღუდვა უშვებს ყველაფერს, რასაც length აქვს — სტრიქონები და მასივები შიგნით, უბრალო რიცხვები გარეთ.
function wrap(x: any): any { return [x] } const r = wrap(42) // r is any r.toExponential() // no error, no help — could crash
function wrap<T>(x: T): T[] { return [x] } const r = wrap(42) // r is number[] r[0].toExponential() // ✓ checked, with autocomplete
ჯენერიკების წყალობით რჩება Promise<T>, Map<K, V> და თქვენი საყვარელი მონაცემთა სტრუქტურები ტიპებით დაცული. მიმართეთ მას ყოველთვის, როცა ფუნქციის გამომავალი ტიპი დამოკიდებულია მის შემავალ ტიპზე.
ტიპები კომპოზირდება. ერთი User ფორმიდან შეგიძლიათ გამოიყვანოთ "პატჩი", სადაც ყველა ველი არასავალდებულოა, read-only ასლი ან id-ით დაინდექსებული საძიებო ცხრილი — და ყველა ორიგინალთან სინქრონში დარჩება. TypeScript ამ გარდაქმნების მთელ ინსტრუმენტთა ყუთს გვთავაზობს, და ისინი აგებულია მაპირებული და პირობითი ტიპებისგან, რომლებსაც თავადაც დაწერთ.
Partial<T>, Pick<T, K> და Record<K, V>. თითოეული სინამდვილეში უბრალოდ მაპირებული ტიპია: ციკლი T-ის გასაღებებზე, რომელიც ყოველ თვისებას გადაწერს. გამოიყვანეთ ტიპები მათი დუბლირების ნაცვლად — შეცვალეთ წყარო ერთხელ და ყოველი გამოყვანილი ტიპი მიჰყვება.Partial გაივლის User-ის ყოველ გასაღებს და ?-ს ამატებს — ერთი გარდაქმნა, ერთგვაროვნად გამოყენებული.
Partial<T> / Required<T> — ყოველ ველს არასავალდებულოდ / სავალდებულოდ აქცევს.Pick<T, K> / Omit<T, K> — ტოვებს / ხსნის გასაღებების ქვესიმრავლეს.Record<K, V> — ობიექტი, რომლის გასაღებებია K და მნიშვნელობები V.Readonly<T>, ReturnType<F>, Awaited<P> — და სხვები.შეინარჩუნეთ ერთი ჭეშმარიტების წყარო. ფორმა მონახაზს არედაქტირებს, ამიტომ მას ყველა ველი არასავალდებულო სჭირდება; საძიებო ცხრილს კი Record უნდა. გამოიყვანეთ ორივე User-იდან და ისინი ვერასდროს დაშორდებიან ერთმანეთს.
მაპირებული ტიპი გასაღებების for-ციკლია: [K in keyof T] ყოველ თვისებას სტუმრობს, რომ მისი მოდიფიკატორი ან ტიპი შეცვალოთ. ზემოთ ჩამოთვლილი ყოველი utility ტიპი სწორედ ასეთია.
T extends U ? X : Y ტიპებისთვის if-ია. დააწყვილეთ ის infer-თან, რომ ერთი ტიპიდან მეორე ამოიღოთ — ზუსტად ასე მუშაობს ReturnType და Awaited.
ძლიერია, მაგრამ ადვილი გადასაჭარბებელი. თუ პირობით ტიპს კოლეგა ათ წუთს კითხულობს, უფრო მარტივი, ცალსახად დაწერილი ტიპი ხშირად უკეთესი არჩევანია.
რამდენად მკაცრად გეწინააღმდეგებათ TypeScript, პარამეტრის საკითხია. strict რეჟიმი იმ შემოწმებების კრებულია, რომლებიც ტიპების სისტემას ნამდვილად სანდოს ხდის — უპირველესად კი null-სა და undefined-ს სერიოზულად აღიქვამს. სამი განსაკუთრებული ტიპი კი წყვეტს, რამდენ უსაფრთხოებას ინარჩუნებთ: any სისტემიდან გადის, unknown უსაფრთხოდ რჩება, never კი ნიშნავს "ვერ მოხდება".
"strict": true დროშა tsconfig.json-ში — ერთბაშად რთავს შემოწმებების მთელ ოჯახს, მათ შორის strictNullChecks-ს (null და undefined აღარ არის ჩუმად ყოველი ტიპის ნაწილი) და noImplicitAny-ს (ტიპი, რომელიც ვერ დაისკვნება, შეცდომაა და არა უფასო any). ახალი პროექტები მკაცრი რეჟიმით დაიწყეთ; მოგვიანებით მისი დამატება ბევრად რთულია.unknown ყველა ტიპზე ზემოთ დგას (და შემოწმებას გაიძულებთ); never ყველას ქვემოთაა; any კი სისტემიდან სულ გარეთ გადის.
any — ამ მნიშვნელობისთვის შემოწმებას თიშავს. ერთმა any-მ შეიძლება ჩუმად დააინფიციროს ყველაფერი, რასაც შეეხება.unknown — "შეიძლება ნებისმიერი იყოს, ამიტომ ჯერ დაამტკიცე, რა არის." უსაფრთხო დასაშვები ადგილი გარე მონაცემებისთვის.never — მნიშვნელობა, რომელიც ვერ იარსებებს: ფუნქცია, რომელიც ყოველთვის აგდებს შეცდომას, ან ამოწურული იუნიონი.as, !) შემმოწმებელს გვერდს უვლის — გამოიყენეთ იშვიათად და გააზრებულად.// any — the checker stops helping let a: any = JSON.parse(body) a.user.name.toUpperCase() // compiles… may explode // unknown — you must prove the shape first let u: unknown = JSON.parse(body) u.user // ✕ 'u' is of type 'unknown' if (typeof u === "object" && u !== null) { // now safely narrow further… }
any ცუდ მონაცემებს კრახამდე ატარებს; unknown კი მათ კარებთანვე აჩერებს, სანამ არ შეამოწმებთ.
სასიცოცხლო, ხშირად გამორჩენილი ფაქტი: ბილდის ინსტრუმენტების უმეტესობა ტიპებს საერთოდ არ ამოწმებს. ისინი მათ სისწრაფისთვის ხსნიან. ტიპების შრე რეალურად მხოლოდ tsc-ს ესმის — ამიტომ ტურბოსწრაფ ბილდსაც კი ცალკე ტიპების შემოწმების ნაბიჯი სჭირდება.
საეტალონო კომპილატორი. აქ ის ერთადერთი ინსტრუმენტია, რომელიც თქვენს ტიპებს რეალურად ამტკიცებს, და ისიც, რომელიც ბიბლიოთეკებისთვის .d.ts დეკლარაციის ფაილებს გამოსცემს.
.d.ts ტიპებს, რომლებსაც მომხმარებლები ეყრდნობიან.tsc --noEmit მაშინაც კი, როცა JS-ს ბანდლერი აწარმოებს.Go-ზე აგებული ბანდლერი/ტრანსპილერი, რომელიც ტიპებს მილიწამებში ხსნის. ის ტიპებს არასდროს უყურებს — უბრალოდ შლის მათ.
tsc --noEmit-თან.Rust-ზე აგებული გარდაქმნა, რომელიც ასაზრდოებს ისეთ ინსტრუმენტებს, როგორიცაა Next.js და სწრაფი ტესტ-რანერები. esbuild-ის მსგავსად, ის ფაილ-ფაილ ტრანსპილირებს და ტიპებს შლის.
tsc შეინარჩუნეთ.@babel/preset-typescript ტიპებს არსებული Babel-ის პაიპლაინის შიგნით ხსნის — მოსახერხებელია, როცა უკვე Babel-ზე ხართ.
const enum და namespace-ები) არ მუშაობს.ტიპის აღნიშვნა კომპილატორისთვის მიცემული დაპირებაა და არა რანტაიმის შემოწმება. JSON.parse, fetch, ფორმის სხეულები და as მტკიცებები — ყველა მათგანს შეუძლია მოგცეთ მნიშვნელობა, რომლის ნამდვილი ფორმაც გამოცხადებულ ტიპს არ ემთხვევა. გამოსავალი მარტივია: დაავალიდირეთ ტიპიზების გარეშე მოსული მონაცემები საზღვარზე, შემდეგ კი შიგნით თქვენს ტიპებს ენდეთ.
JSON.parse-იდან შემოდის, any/unknown-ად ჩამოდის; შიშველი as User უბრალოდ ამტკიცებს ფორმას, რომელიც არავის შეუმოწმებია. დაპარსეთ ის და ნუ ივარაუდებთ.დაავალიდირეთ ერთხელ საზღვარზე; ყველაფერი ამ ხაზის შიგნით უკვე თავის ტიპებს ენდობა.
as const — ლიტერალს მის ყველაზე ვიწრო ტიპზე აფიქსირებს ("circle" რჩება "circle"-ად და არა string-ად) — იდეალურია კონფიგურაციისა და იუნიონის ტეგებისთვის.satisfies — ამოწმებს მნიშვნელობას ტიპთან ისე, რომ არ აფართოებს მას, ასე რომ გრჩებათ გარანტიაც და დასკვნილი ზუსტი ტიპიც.const routes = { home: "/", docs: "/docs", } satisfies Record<string, string> routes.home // still exactly "/" — not widened
const res = await fetch(url) const user = await res.json() as User // "as" verifies NOTHING — if the API changed, // user.name is undefined and you crash downstream
const User = z.object({ id: z.number(), name: z.string() }) const res = await fetch(url) const user = User.parse(await res.json()) // throws on bad data; user is typed AND real
ეს ბიბლიოთეკები საშუალებას გაძლევთ, სქემა ერთხელ დაწეროთ და TypeScript-ის ტიპი მისგან დაასკვნათ — ერთი ჭეშმარიტების წყარო, რომელიც კომპილაციასაც ფარავს და რანტაიმსაც. აირჩიეთ თქვენი პრიორიტეტების მიხედვით.
მოქნილი სქემის მშენებელი; z.infer სტატიკურ ტიპს გამოჰყავს, ეკოსისტემა კი (ფორმები, RPC, ORM-ები) უზარმაზარია.
ვალიდატორები დამოუკიდებელი ფუნქციებია, რომლებსაც pipe-ით აწყობთ, ამიტომ ბანდლერები ყველაფერს, რასაც არ აიმპორტებთ, tree-shaking-ით აცილებს.
კოდეკები, აგებული fp-ts-ზე; დეკოდირება აბრუნებს Either-ს — ან შეცდომებს, ან ტიპიზებულ მნიშვნელობას — ძლიერი კომპოზიციურობით.
fp-ts-ის ციცაბო სასწავლი მრუდი; ახალ ვარიანტებზე ნაკლებად აქტიური.fp-ts-ს იყენებენ და ფუნქციური სიმკაცრე ბოლომდე სურთ.any-ს. გამომძახებლის ტიპი გამჭოლად ატარეთ; ტიპები utility-ებით გამოიყვანეთ და ნუ გაამრავლებთ.tsc. მკაცრი რეჟიმი პლუს ტიპების შემოწმების ნამდვილი ნაბიჯი — მარტო ტრანსპილერები არასდროს ამოწმებენ.ხუთი სწრაფი კითხვა სტრუქტურულ ტიპიზაციაზე, დავიწროებაზე, ჯენერიკებზე, მკაცრ რეჟიმსა და იმაზე, სად ცრუობს ტიპები — მყისიერი უკუკავშირი, ავტორიზაციის გარეშე.
ნავიგაცია ← → ღილაკებით ან სქროლით · უკან ბიბლიოთეკაში