Latviešu

Apgūstiet TypeScript lieko īpašību pārbaudes, lai novērstu izpildlaika kļūdas un uzlabotu objektu tipu drošību stabilām, paredzamām JavaScript lietojumprogrammām.

TypeScript lieko īpašību pārbaudes: Jūsu objektu tipu drošības stiprināšana

Mūsdienu programmatūras izstrādes jomā, īpaši ar JavaScript, koda integritātes un paredzamības nodrošināšana ir vissvarīgākā. Lai gan JavaScript piedāvā milzīgu elastību, tas dažkārt var izraisīt izpildlaika kļūdas neparedzētu datu struktūru vai īpašību neatbilstību dēļ. Šeit izceļas TypeScript, nodrošinot statiskās tipēšanas iespējas, kas uztver daudzas bieži sastopamas kļūdas, pirms tās parādās produkcijā. Viena no TypeScript jaudīgākajām, bet dažkārt pārprastajām funkcijām ir tā lieko īpašību pārbaude.

Šis raksts dziļi iedziļinās TypeScript lieko īpašību pārbaudēs, paskaidrojot, kas tās ir, kāpēc tās ir būtiskas objektu tipu drošībai un kā tās efektīvi izmantot, lai veidotu stabilākas un paredzamākas lietojumprogrammas. Mēs izpētīsim dažādus scenārijus, biežākās kļūdas un labākās prakses, lai palīdzētu izstrādātājiem visā pasaulē, neatkarīgi no viņu pieredzes, izmantot šo svarīgo TypeScript mehānismu.

Pamatkoncepcijas izpratne: Kas ir lieko īpašību pārbaudes?

Būtībā TypeScript lieko īpašību pārbaude ir kompilatora mehānisms, kas neļauj piešķirt objekta literāli mainīgajam, kura tips skaidri neatļauj šīs papildu īpašības. Vienkāršāk sakot, ja definējat objekta literāli un mēģināt to piešķirt mainīgajam ar noteiktu tipa definīciju (piemēram, saskarni vai tipa aizstājvārdu), un šis literālis satur īpašības, kas nav deklarētas definētajā tipā, TypeScript to atzīmēs kā kļūdu kompilēšanas laikā.

Ilustrēsim to ar vienkāršu piemēru:


interface User {
  name: string;
  age: number;
}

const newUser: User = {
  name: 'Alice',
  age: 30,
  email: 'alice@example.com' // Kļūda: Objekta literālis drīkst norādīt tikai zināmas īpašības, un 'email' nepastāv tipā 'User'.
};

Šajā fragmentā mēs definējam `interface` ar nosaukumu `User` ar divām īpašībām: `name` un `age`. Kad mēs mēģinām izveidot objekta literāli ar papildu īpašību `email` un piešķirt to mainīgajam ar tipu `User`, TypeScript nekavējoties atklāj neatbilstību. Īpašība `email` ir "lieka" īpašība, jo tā nav definēta `User` saskarnē. Šī pārbaude tiek veikta tieši tad, kad piešķiršanai izmantojat objekta literāli.

Kāpēc lieko īpašību pārbaudes ir svarīgas?

Lieko īpašību pārbaužu nozīme slēpjas to spējā ieviest līgumu starp jūsu datiem un to paredzamo struktūru. Tās veicina objektu tipu drošību vairākos būtiskos veidos:

Kad tiek piemērotas lieko īpašību pārbaudes?

Ir ļoti svarīgi saprast konkrētos nosacījumus, kādos TypeScript veic šīs pārbaudes. Tās galvenokārt tiek piemērotas objektu literāļiem, kad tie tiek piešķirti mainīgajam vai nodoti kā arguments funkcijai.

1. scenārijs: Objektu literāļu piešķiršana mainīgajiem

Kā redzams iepriekšējā `User` piemērā, tieša objekta literāļa ar papildu īpašībām piešķiršana tipizētam mainīgajam izraisa pārbaudi.

2. scenārijs: Objektu literāļu nodošana funkcijām

Kad funkcija sagaida argumentu ar noteiktu tipu, un jūs nododat objekta literāli, kas satur liekas īpašības, TypeScript to atzīmēs.


interface Product {
  id: number;
  name: string;
}

function displayProduct(product: Product): void {
  console.log(`Product ID: ${product.id}, Name: ${product.name}`);
}

displayProduct({
  id: 101,
  name: 'Laptop',
  price: 1200 // Kļūda: Arguments ar tipu '{ id: number; name: string; price: number; }' nav piešķirams parametram ar tipu 'Product'.
             // Objekta literālis drīkst norādīt tikai zināmas īpašības, un 'price' nepastāv tipā 'Product'.
});

Šeit `price` īpašība objekta literālī, kas nodots funkcijai `displayProduct`, ir lieka īpašība, jo `Product` saskarne to nedefinē.

Kad lieko īpašību pārbaudes *netiek* piemērotas?

Izpratne par to, kad šīs pārbaudes tiek apietas, ir tikpat svarīga, lai izvairītos no neskaidrībām un zinātu, kad varētu būt nepieciešamas alternatīvas stratēģijas.

1. Ja piešķiršanai neizmanto objektu literāļus

Ja piešķirat objektu, kas nav objekta literālis (piemēram, mainīgais, kurā jau ir objekts), lieko īpašību pārbaude parasti tiek apieta.


interface Config {
  timeout: number;
}

function setupConfig(config: Config) {
  console.log(`Timeout set to: ${config.timeout}`);
}

const userProvidedConfig = {
  timeout: 5000,
  retries: 3 // Šī 'retries' īpašība ir lieka saskaņā ar 'Config'
};

setupConfig(userProvidedConfig); // Nav kļūdas!

// Lai gan userProvidedConfig ir papildu īpašība, pārbaude tiek izlaista
// jo tas nav objekta literālis, kas tiek nodots tieši.
// TypeScript pārbauda paša userProvidedConfig tipu.
// Ja userProvidedConfig būtu deklarēts ar tipu Config, kļūda rastos agrāk.
// Tomēr, ja tas ir deklarēts kā 'any' vai plašāks tips, kļūda tiek atlikta.

// Precīzāks veids, kā parādīt apiešanu:
let anotherConfig;

if (Math.random() > 0.5) {
  anotherConfig = {
    timeout: 1000,
    host: 'localhost' // Lieka īpašība
  };
} else {
  anotherConfig = {
    timeout: 2000,
    port: 8080 // Lieka īpašība
  };
}

setupConfig(anotherConfig as Config); // Nav kļūdas tipa apgalvojuma un apiešanas dēļ

// Galvenais ir tas, ka 'anotherConfig' nav objekta literālis brīdī, kad to piešķir setupConfig.
// Ja mums būtu starpposma mainīgais ar tipu 'Config', sākotnējā piešķiršana neizdotos.

// Starpposma mainīgā piemērs:
let intermediateConfig: Config;

intermediateConfig = {
  timeout: 3000,
  logging: true // Kļūda: Objekta literālis drīkst norādīt tikai zināmas īpašības, un 'logging' nepastāv tipā 'Config'.
};

Pirmajā `setupConfig(userProvidedConfig)` piemērā `userProvidedConfig` ir mainīgais, kas satur objektu. TypeScript pārbauda, vai `userProvidedConfig` kopumā atbilst `Config` tipam. Tas nepiemēro stingro objekta literāļa pārbaudi pašam `userProvidedConfig`. Ja `userProvidedConfig` būtu deklarēts ar tipu, kas neatbilst `Config`, kļūda rastos tā deklarēšanas vai piešķiršanas laikā. Apiešana notiek, jo objekts jau ir izveidots un piešķirts mainīgajam, pirms tas tiek nodots funkcijai.

2. Tipu apgalvojumi

Jūs varat apiet lieko īpašību pārbaudes, izmantojot tipu apgalvojumus, lai gan tas jādara piesardzīgi, jo tas ignorē TypeScript drošības garantijas.


interface Settings {
  theme: 'dark' | 'light';
}

const mySettings = {
  theme: 'dark',
  fontSize: 14 // Lieka īpašība
} as Settings;

// Šeit nav kļūdas tipa apgalvojuma dēļ.
// Mēs sakām TypeScript: "Uzticies man, šis objekts atbilst Settings."
console.log(mySettings.theme);
// console.log(mySettings.fontSize); // Tas izraisītu izpildlaika kļūdu, ja fontSize faktiski nebūtu.

3. Indeksu parakstu vai izklāšanas sintakses izmantošana tipu definīcijās

Ja jūsu saskarne vai tipa aizstājvārds skaidri atļauj patvaļīgas īpašības, lieko īpašību pārbaudes netiks piemērotas.

Indeksu parakstu izmantošana:


interface FlexibleObject {
  id: number;
  [key: string]: any; // Atļauj jebkuru virknes atslēgu ar jebkuru vērtību
}

const flexibleItem: FlexibleObject = {
  id: 1,
  name: 'Widget',
  version: '1.0.0'
};

// Nav kļūdas, jo 'name' un 'version' ir atļautas ar indeksa parakstu.
console.log(flexibleItem.name);

Izklāšanas sintakses izmantošana tipu definīcijās (retāk tiek izmantota tiešai pārbaužu apiešanai, vairāk - saderīgu tipu definēšanai):

Lai gan tas nav tiešs apiešanas veids, izklāšana ļauj izveidot jaunus objektus, kas ietver esošās īpašības, un pārbaude attiecas uz jaunizveidoto literāli.

4. `Object.assign()` vai izklāšanas sintakses izmantošana apvienošanai

Kad izmantojat `Object.assign()` vai izklāšanas sintaksi (`...`), lai apvienotu objektus, lieko īpašību pārbaude darbojas atšķirīgi. Tā attiecas uz izveidoto rezultējošo objekta literāli.


interface BaseConfig {
  host: string;
}

interface ExtendedConfig extends BaseConfig {
  port: number;
}

const defaultConfig: BaseConfig = {
  host: 'localhost'
};

const userConfig = {
  port: 8080,
  timeout: 5000 // Lieka īpašība attiecībā pret BaseConfig, bet gaidīta apvienotajā tipā
};

// Izklāšana jaunā objekta literālī, kas atbilst ExtendedConfig
const finalConfig: ExtendedConfig = {
  ...defaultConfig,
  ...userConfig
};

// Tas parasti ir kārtībā, jo 'finalConfig' ir deklarēts kā 'ExtendedConfig' un īpašības saskan. Pārbaude notiek pēc 'finalConfig' tipa.

// Apskatīsim scenāriju, kur tas *neizdotos*:

interface SmallConfig {
  key: string;
}

const data1 = { key: 'abc', value: 123 }; // 'value' šeit ir lieka
const data2 = { key: 'xyz', status: 'active' }; // 'status' šeit ir lieka

// Mēģinājums piešķirt tipam, kas nepieņem papildu īpašības

// const combined: SmallConfig = {
//   ...data1, // Kļūda: Objekta literālis drīkst norādīt tikai zināmas īpašības, un 'value' nepastāv tipā 'SmallConfig'.
//   ...data2  // Kļūda: Objekta literālis drīkst norādīt tikai zināmas īpašības, un 'status' nepastāv tipā 'SmallConfig'.
// };

// Kļūda rodas, jo objekta literālis, kas izveidots ar izklāšanas sintaksi,
// satur īpašības ('value', 'status'), kas nav atrodamas 'SmallConfig'.

// Ja mēs izveidojam starpposma mainīgo ar plašāku tipu:

const temp: any = {
  ...data1,
  ...data2
};

// Pēc tam piešķirot SmallConfig, lieko īpašību pārbaude tiek apieta sākotnējā literāļa izveidē,
// bet tipa pārbaude piešķiršanas brīdī joprojām var notikt, ja temp tips tiek secināts stingrāk.
// Tomēr, ja temp ir 'any', pārbaude nenotiek līdz piešķiršanai 'combined'.

// Precizēsim izpratni par izklāšanu ar lieko īpašību pārbaudēm:
// Pārbaude notiek, kad objekta literālis, kas izveidots ar izklāšanas sintaksi, tiek piešķirts
// mainīgajam vai nodots funkcijai, kas sagaida specifiskāku tipu.

interface SpecificShape { 
  id: number;
}

const objA = { id: 1, extra1: 'hello' };
const objB = { id: 2, extra2: 'world' };

// Šis neizdosies, ja SpecificShape neatļauj 'extra1' vai 'extra2':
// const merged: SpecificShape = {
//   ...objA,
//   ...objB
// };

// Iemesls, kāpēc tas neizdodas, ir tas, ka izklāšanas sintakse faktiski izveido jaunu objekta literāli.
// Ja objA un objB būtu pārklājošās atslēgas, pēdējā uzvarētu. Kompilators
// redz šo rezultējošo literāli un pārbauda to pret 'SpecificShape'.

// Lai tas darbotos, jums varētu būt nepieciešams starpposms vai pielaidīgāks tips:

const tempObj = {
  ...objA,
  ...objB
};

// Tagad, ja tempObj ir īpašības, kas nav SpecificShape, piešķiršana neizdosies:
// const mergedCorrected: SpecificShape = tempObj; // Kļūda: Objekta literālis drīkst norādīt tikai zināmas īpašības...

// Galvenais ir tas, ka kompilators analizē veidojamā objekta literāļa formu.
// Ja šis literālis satur īpašības, kas nav definētas mērķa tipā, tā ir kļūda.

// Tipisks izklāšanas sintakses lietošanas gadījums ar lieko īpašību pārbaudēm:

interface UserProfile {
  userId: string;
  username: string;
}

interface AdminProfile extends UserProfile {
  adminLevel: number;
}

const baseUserData: UserProfile = {
  userId: 'user-123',
  username: 'coder'
};

const adminData = {
  adminLevel: 5,
  lastLogin: '2023-10-27'
};

// Šeit ir svarīga lieko īpašību pārbaude:
// const adminProfile: AdminProfile = {
//   ...baseUserData,
//   ...adminData // Kļūda: Objekta literālis drīkst norādīt tikai zināmas īpašības, un 'lastLogin' nepastāv tipā 'AdminProfile'.
// };

// Objekta literālim, kas izveidots ar izklāšanu, ir 'lastLogin', kas nav 'AdminProfile'.
// Lai to labotu, 'adminData' ideālā gadījumā būtu jāatbilst AdminProfile, vai arī liekā īpašība būtu jāapstrādā.

// Labotā pieeja:
const validAdminData = {
  adminLevel: 5
};

const adminProfileCorrect: AdminProfile = {
  ...baseUserData,
  ...validAdminData
};

console.log(adminProfileCorrect.userId);
console.log(adminProfileCorrect.adminLevel);

Lieko īpašību pārbaude attiecas uz rezultējošo objekta literāli, kas izveidots ar izklāšanas sintaksi. Ja šis rezultējošais literālis satur īpašības, kas nav deklarētas mērķa tipā, TypeScript ziņos par kļūdu.

Stratēģijas lieko īpašību apstrādei

Lai gan lieko īpašību pārbaudes ir noderīgas, pastāv leģitīmi scenāriji, kuros jums varētu būt papildu īpašības, kuras vēlaties iekļaut vai apstrādāt atšķirīgi. Šeit ir izplatītas stratēģijas:

1. Atlikuma īpašības ar tipu aizstājvārdiem vai saskarnēm

Jūs varat izmantot atlikuma parametru sintaksi (`...rest`) tipu aizstājvārdos vai saskarnēs, lai uztvertu visas atlikušās īpašības, kas nav skaidri definētas. Tas ir tīrs veids, kā atzīt un apkopot šīs liekās īpašības.


interface UserProfile {
  id: number;
  name: string;
}

interface UserWithMetadata extends UserProfile {
  metadata: {
    [key: string]: any;
  };
}

// Vai biežāk ar tipa aizstājvārdu un atlikuma sintaksi:
type UserProfileWithMetadata = UserProfile & {
  [key: string]: any;
};

const user1: UserProfileWithMetadata = {
  id: 1,
  name: 'Bob',
  email: 'bob@example.com',
  isAdmin: true
};

// Nav kļūdas, jo 'email' un 'isAdmin' tiek uztvertas ar indeksa parakstu UserProfileWithMetadata.
console.log(user1.email);
console.log(user1.isAdmin);

// Cits veids, izmantojot atlikuma parametrus tipa definīcijā:
interface ConfigWithRest {
  apiUrl: string;
  timeout?: number;
  // Uztvert visas pārējās īpašības 'extraConfig'
  [key: string]: any;
}

const appConfig: ConfigWithRest = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  featureFlags: {
    newUI: true,
    betaFeatures: false
  }
};

console.log(appConfig.featureFlags);

Izmantojot `[key: string]: any;` vai līdzīgus indeksa parakstus, ir idiomātisks veids, kā apstrādāt patvaļīgas papildu īpašības.

2. Destrukturēšana ar atlikuma sintaksi

Kad saņemat objektu un nepieciešams iegūt konkrētas īpašības, vienlaikus saglabājot pārējās, destrukturēšana ar atlikuma sintaksi ir nenovērtējama.


interface Employee {
  employeeId: string;
  department: string;
}

function processEmployeeData(data: Employee & { [key: string]: any }) {
  const { employeeId, department, ...otherDetails } = data;

  console.log(`Employee ID: ${employeeId}`);
  console.log(`Department: ${department}`);
  console.log('Other details:', otherDetails);
  // otherDetails saturēs visas īpašības, kas nav skaidri destrukturētas,
  // piemēram, 'salary', 'startDate' utt.
}

const employeeInfo = {
  employeeId: 'emp-789',
  department: 'Engineering',
  salary: 90000,
  startDate: '2022-01-15'
};

processEmployeeData(employeeInfo);

// Pat ja employeeInfo sākotnēji bija papildu īpašība, lieko īpašību pārbaude
// tiek apieta, ja funkcijas paraksts to pieņem (piemēram, izmantojot indeksa parakstu).
// Ja processEmployeeData būtu stingri tipizēts kā 'Employee', un employeeInfo būtu 'salary',
// kļūda rastos, JA employeeInfo būtu objekta literālis, kas nodots tieši.
// Bet šeit employeeInfo ir mainīgais, un funkcijas tips apstrādā papildu īpašības.

3. Visu īpašību skaidra definēšana (ja zināmas)

Ja zināt potenciālās papildu īpašības, labākā pieeja ir pievienot tās savai saskarnei vai tipa aizstājvārdam. Tas nodrošina visaugstāko tipu drošību.


interface UserProfile {
  id: number;
  name: string;
  email?: string; // Neobligāts e-pasts
}

const userWithEmail: UserProfile = {
  id: 2,
  name: 'Charlie',
  email: 'charlie@example.com'
};

const userWithoutEmail: UserProfile = {
  id: 3,
  name: 'David'
};

// Ja mēģinām pievienot īpašību, kas nav UserProfile:
// const userWithExtra: UserProfile = {
//   id: 4,
//   name: 'Eve',
//   phoneNumber: '555-1234'
// }; // Kļūda: Objekta literālis drīkst norādīt tikai zināmas īpašības, un 'phoneNumber' nepastāv tipā 'UserProfile'.

4. `as` izmantošana tipu apgalvojumiem (ar piesardzību)

Kā parādīts iepriekš, tipu apgalvojumi var nomākt lieko īpašību pārbaudes. Izmantojiet to reti un tikai tad, ja esat pilnīgi pārliecināts par objekta formu.


interface ProductConfig {
  id: string;
  version: string;
}

// Iedomājieties, ka tas nāk no ārēja avota vai mazāk stingra moduļa
const externalConfig = {
  id: 'prod-abc',
  version: '1.2',
  debugMode: true // Lieka īpašība
};

// Ja zināt, ka 'externalConfig' vienmēr būs 'id' un 'version' un vēlaties to uzskatīt par ProductConfig:
const productConfig = externalConfig as ProductConfig;

// Šis apgalvojums apiet lieko īpašību pārbaudi pašam `externalConfig`.
// Tomēr, ja jūs nodotu objekta literāli tieši:

// const productConfigLiteral: ProductConfig = {
//   id: 'prod-xyz',
//   version: '2.0',
//   debugMode: false
// }; // Kļūda: Objekta literālis drīkst norādīt tikai zināmas īpašības, un 'debugMode' nepastāv tipā 'ProductConfig'.

5. Tipu aizsargi

Sarežģītākos scenārijos tipu aizsargi var palīdzēt sašaurināt tipus un nosacīti apstrādāt īpašības.


interface Shape {
  kind: 'circle' | 'square';
}

interface Circle extends Shape {
  kind: 'circle';
  radius: number;
}

interface Square extends Shape {
  kind: 'square';
  sideLength: number;
}

function calculateArea(shape: Shape) {
  if (shape.kind === 'circle') {
    // TypeScript šeit zina, ka 'shape' ir Circle
    console.log(Math.PI * shape.radius ** 2);
  } else if (shape.kind === 'square') {
    // TypeScript šeit zina, ka 'shape' ir Square
    console.log(shape.sideLength ** 2);
  }
}

const circleData = {
  kind: 'circle' as const, // Izmantojot 'as const' literāļa tipa secināšanai
  radius: 10,
  color: 'red' // Lieka īpašība
};

// Kad nodots funkcijai calculateArea, funkcijas paraksts sagaida 'Shape'.
// Pati funkcija pareizi piekļūs 'kind'.
// Ja calculateArea gaidītu 'Circle' tieši un saņemtu circleData kā objekta literāli, 'color' būtu problēma.

// Ilustrēsim lieko īpašību pārbaudi ar funkciju, kas sagaida konkrētu apakštipu:

function processCircle(circle: Circle) {
  console.log(`Processing circle with radius: ${circle.radius}`);
}

// processCircle(circleData); // Kļūda: Arguments ar tipu '{ kind: "circle"; radius: number; color: string; }' nav piešķirams parametram ar tipu 'Circle'.
                         // Objekta literālis drīkst norādīt tikai zināmas īpašības, un 'color' nepastāv tipā 'Circle'.

// Lai to labotu, jūs varat destrukturēt vai izmantot pielaidīgāku tipu priekš circleData:

const { color, ...circleDataWithoutColor } = circleData;
processCircle(circleDataWithoutColor);

// Vai definēt circleData, lai iekļautu plašāku tipu:

const circleDataWithExtras: Circle & { [key: string]: any } = {
  kind: 'circle',
  radius: 15,
  color: 'blue'
};
processCircle(circleDataWithExtras); // Tagad tas darbojas.

Biežākās kļūdas un kā no tām izvairīties

Pat pieredzējuši izstrādātāji dažkārt var tikt pārsteigti ar lieko īpašību pārbaudēm. Šeit ir biežākās kļūdas:

Globāli apsvērumi un labākās prakses

Strādājot globālā, daudzveidīgā izstrādes vidē, konsekventu prakšu ievērošana attiecībā uz tipu drošību ir ļoti svarīga:

Noslēgums

TypeScript lieko īpašību pārbaudes ir stūrakmens tā spējai nodrošināt robustu objektu tipu drošību. Saprotot, kad un kāpēc šīs pārbaudes notiek, izstrādātāji var rakstīt paredzamāku kodu ar mazāk kļūdām.

Izstrādātājiem visā pasaulē šīs funkcijas pieņemšana nozīmē mazāk pārsteigumu izpildlaikā, vieglāku sadarbību un vieglāk uzturamas kodu bāzes. Neatkarīgi no tā, vai veidojat nelielu palīgrīku vai liela mēroga uzņēmuma lietojumprogrammu, lieko īpašību pārbaužu apgūšana neapšaubāmi paaugstinās jūsu JavaScript projektu kvalitāti un uzticamību.

Galvenās atziņas:

Apzināti piemērojot šos principus, jūs varat ievērojami uzlabot sava TypeScript koda drošību un uzturēšanu, kas noved pie veiksmīgākiem programmatūras izstrādes rezultātiem.