În TypeScript, ce înseamnă <T>?
Ia în considerare:
export declare function createEntityAdapter<T>(options?: {
selectId?: IdSelector<T>;
sortComparer?: false | Comparer<T>;
}): EntityAdapter<T>;
Ce înseamnă <T>? Știu că <> este o afirmație de tip, dar nu știu ce este 'T'. Ce face această funcție?
Răspuns #1
Ce înseamnă
<T>?
Aceasta este declarația de generice a TypeScript.
Fragment :
O parte importantă a ingineriei software constă în construirea de componente care nu numai că au API-uri bine definite și consistente, dar sunt și reutilizabile. Componentele care sunt capabile să funcționeze atât cu datele actuale, cât și cu datele viitoare vă vor oferi cele mai flexibile capacități pentru construirea de sisteme software mari.
În limbaje precum C# și Java, unul dintre principalele instrumente din trusa de unelte pentru crearea de componente reutilizabile este genericele, adică posibilitatea de a crea o componentă care poate funcționa cu o varietate de tipuri, și nu doar cu unul singur. Aceasta le permite utilizatorilor să consume aceste componente și să folosească propriile lor tipuri.
Ați menționat:
Nu știu ce este 'T'.
'T' va fi un tip declarat la rulare în loc de timpul de compilare. Variabila T ar putea fi orice variabilă nedeclarată (nu am găsit o referință, dar aș presupune orice set valid de caractere care ar putea fi folosit pentru numele variabilelor). Similar, în C#, dacă tipul pe care T îl reprezintă nu este un tip valoare, ci un tip mai complex (clasă) sau interfață, acesta ar putea fi numit/declarat ca TVehicle sau TAnimal pentru a ajuta la denotarea unui tip valid pentru programatorii viitori (și ar putea fi considerată o bună practică deoarece doar T nu este intuitiv). Prefer TSomething deoarece știu că T mare înseamnă un tip generic. WSometing sau ASomething sunt de asemenea valide, dar pur și simplu nu le prefer. (API-urile Microsoft sunt aproape întotdeauna TContext sau TEntity, de exemplu).
Ar fi de asemenea util dacă cineva mi-ar putea explica ce face această funcție.
Ei bine, funcția nu face nimic. Acesta este mai degrabă un fel de a declara un tip de funcție care poate avea mai multe valori de tip la rulare. În loc să explic asta, voi include un extras luat direct din linkul de mai sus.
function identity<T>(arg: T): T {
return arg;
}
care poate fi folosit astfel:
// type of output will be 'string'
let output = identity<string>("myString");
sau
// type of output will be 'string', the compiler will figure out `T`
// based on the value passed in
let output = identity("myString");
sau
// type of output will be 'number'
let output = identity(8675309);
Care ar putea ridica întrebarea:
De ce să folosești generice
JavaScript are array-uri, dar atunci când obții o valoare din array, aceasta ar putea fi literalmente orice (TypeScript: any). Cu TypeScript obții siguranță a tipurilor declarându-le astfel:
// Array<T>
let list: number[] = [1, 2, 3];
// or
let list: Array<number> = [1, 2, 3];
Acum fiecare valoare din array are un tip. TypeScript va genera o eroare la compilare dacă încerci să pui un șir de caractere în acest array. Și obții siguranță a tipului și intellisense (în funcție de editorul tău) atunci când preiei o valoare:
class Person {
FirstName: string;
}
let people: Array<Person> = [];
people.push({ FirstName: "John" } as Person);
let john = people.pop();
// john is of type Person, the TypeScript compiler knows this
// because we've declared the people variable as an array of Person
console.log(john.FirstName);
Declararea constrângerilor generice tipizate. Un exemplu foarte bun al Principiului Open - Closed.
În programarea orientată pe obiecte, principiul open/closed afirmă „entitățile software (clase, module, funcții etc.) ar trebui să fie deschise pentru extensie, dar închise pentru modificare”; adică, o astfel de entitate poate permite extinderea comportamentului său fără a-i modifica codul sursă.
În exemplul următor, oricine ar putea extinde Human sau Cheetah sau chiar să-și creeze propriul tip derivat, iar funcționalitatea Logger ar continua să funcționeze fără nicio modificare.
interface IAnimal {
LegCount: number;
}
class Cheetah
implements IAnimal {
LegCount: number = 4;
}
class Human
implements IAnimal {
LegCount: number = 2;
}
public class Logger<TAnimal extends IAnimal> {
public Log(animal: TAnimal) {
console.log(animal.LegCount);
}
}
var logger = new Logger();
var human = new Human();
logger.Log(human);
Exemplu funcțional
În exemplul anterior am folosit o constrângere generică pentru a limita tipul TAnimal pe care programatorii îl pot folosi pentru a crea o instanță Logger la tipuri care derivă din interfața IAnimal. Acest lucru îi permite compilatorului să presupună că tipul de valoare transmis clasei Logger are întotdeauna o proprietate LegCount.
Poți să-mi explici de ce în documentația TypeScript au pus <T> în loc să pună ceva mai descriptiv, cum ar fi <Identity> de exemplu. Pentru mine, Nothing și <T> sunt același lucru. Acum, toți folosesc <T> ca niște proști sau am ratat ceva?
Toate acestea vor fi presupuneri în continuare. Nu cunosc nici echipa care a proiectat sistemul generic TypeScript, nici echipa care a scris documentația.
La nivelul de bază al genericelor se află capacitatea de a folosi T ca orice tip posibil (să nu fie confundat cu any din TypeScript). Asta înseamnă că Array<T> este interfața (din lipsă unui cuvânt mai bun) și când creăm un tip concret înlocuim T cu un tip declarat:
Array<number>
Deci, pentru interfața Array<T>, ce ar avea mai mult sens decât T? Nu știu. Știu că T trebuie să fie un Tip (număr, șir de caractere etc.), deci are sens să folosim T deoarece este prima literă a cuvântului Tip. Cred că Array<Tip> ar fi foarte confuz și/sau ar putea chiar să fie invalid dacă type sau Type ar deveni rezervate sau restricționate (în prezent type are înțeles special în anumite contexte, deci este de asemenea o alegere proastă), așa că evitarea lor este o alegere bună. Alte limbaje (C-sharp, Java) aleg de asemenea să folosească T, așa că schimbarea între limbaje și posibilitatea de a folosi același termen este avantajoasă.
Pe de altă parte, ce ar însemna următoarele?
Array<Identity>
Ce este Identity aici? Nu există nicio constrângere care să îi ajute pe alți dezvoltatori sau pe dezvoltatorii viitori să știe ce este. Mi s-ar părea că este un Array cu tip specific pe care trebuie să îl implementez în mod explicit, ceea ce înseamnă că nu depinde de mine să aleg tipul generic.
interface Foo1 {
bars: Array<Identity>;
}
În exemplul anterior, eu (și probabil cei mai mulți dezvoltatori) aș presupune că Identity este un tip existent și că nu îl pot schimba.
interface Foo2<T> {
bars: Array<T>;
}
Cu Foo2 știu că trebuie să aleg un tip.
interface Foo3<Identity> {
bars: Array<Identity>;
}
Foo3 este doar confuz.
interface Foo4<TIdentity> {
bars: Array<TIdentity>;
}
Acum, cu Foo4, sunt mult mai încrezător că trebuie să aleg tipul, dar încă sunt puțin confuz de ce TIdentity. Evident, în anumite contexte, unde tipul este mai definit, ar avea sens.
Începând cu martie 2021, documentația TypeScript a fost actualizată pentru a marca drept învechit utilizarea <T> în documentație, favorizând în schimb <Type>.
Fiind astfel, eu personal găsesc următoarele confuz și aș sugera cu tărie evitarea folosirii Type din două motive.
interface Lengthwise {
length: number;
}
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
console.log(arg.length);
return arg;
}
Primul motiv pentru a evita să denumești tipul tău Type atunci când folosești constrângeri este că nu se citește foarte bine în cadrul metodei sau în cazul mai multor metode. Imaginează-ți că metodele de logare sunt mult mai lungi de 2 linii și, dintr-o dată, tot ce citești este cuvântul Type peste tot.
interface Lengthwise {
length: number;
}
interface Widthwise {
width: number;
}
function loggingLength<Type extends Lengthwise>(arg: Type): Type {
console.log(arg.length);
return arg;
}
function loggingWidth<Type extends Widthwise >(arg: Type): Type {
console.log(arg.width);
return arg;
}
Cred că următorul este mult mai ușor de citit:
interface Lengthwise {
length: number;
}
interface Widthwise {
width: number;
}
function loggingLength<TLengthwise extends Lengthwise>(arg: TLengthwise): TLengthwise {
console.log(arg.length);
return arg;
}
function loggingWidth<TWidthwise extends Widthwise>(arg: TWidthwise ): TWidthwise {
console.log(arg.width);
return arg;
}
În al doilea rând, este foarte posibil să ai mai mult de un generic și asta face ca următorul să fie invalid (sper că din motive evidente).
interface Lengthwise {
length: number;
}
interface Widthwise {
width: number;
}
function loggingLength<Type extends Lengthwise, Type extends Widthwise>
(arg1: Type, arg2: Type) {
console.log(arg1.length);
console.log(arg2.width);
}
În timp ce documentația folosește Type pentru a prezenta exemple simple și banale, recomand cu tărie să nu folosiți Type în niciun cod real.
Răspunsul #2
Este demn de menționat că, începând cu martie 2021, documentația TypeScript a fost actualizată pentru a descuraja utilizarea <T> în documentație, în favoarea <Type>. Link.
Răspunsul #3
Exemplul pe care îl oferiți este o funcție cu un parametru generic. T (care nu trebuie să fie T. Ați putea să îl numiți G.) este numită un șablon generic unde tipul real al T este înlocuit la runtime.
Imaginați-vă că EntityAdapter are următoarea implementare:
interface EntityAdapter<T> {
save(entity: T);
}
Codul de mai jos returnează un EntityAdapter al cărui conținut este any. any poate fi număr, șir, obiect sau orice altceva.
let adapter1 = createEntityAdapter<any>(<parameters here>)
Codul de mai jos returnează un EntityAdapter a cărui conținut este Car.
let adapter2 = createEntityAdapter<Car>(<parameters here>)
Practic, Car este mai specific decât any, astfel încât poți obține o siguranță suplimentară a tipului. Deci, cum te ajută asta?
Pe scurt, un șablon generic poate să-ți ofere siguranță suplimentară a tipului. De exemplu,
adapter1.save('I am string') // this works because `T` is `any`
adapter1.save(new Car()) // this also works because `T` is `any`
adapter2.save('I am string') // this wont works because `T` is `Car`, typescript compiler will complains
adapter2.save(new Car()) //this works because `T` is `Car`
Sper că acest lucru ajută.
Răspuns #4
Se numește Generics, ne ajută să scriem funcții sigure, dar flexibile. Oferă variabile pentru tipuri. Pe scurt, poți crea o funcție, interfață sau clasă care funcționează cu diferite tipuri, fără a specifica tipul de la început. Mai jos este un cod demo pentru a explica de ce îl folosim și cum să îl folosim.
// any is too general ,and it loses the meaning of using typescript
function insertAtBegining(array:any[],value:any) {
let newArr = [value, ...array];
return newArr;
}
// if we specify the type number , this function will lost it's utility
function insertAtBegining(array:number[],value:number) {
let newArr = [value, ...array];
return newArr;
}
// if we specify the type string , this function will lost it's utility
function insertAtBegining(array:string[],value:string) {
let newArr = [value, ...array];
return newArr;
}
const demoArr = [1, 2, 3];
const updatedArr = insertAtBegining(demoArr, 4);
// more generic functions
function insertAtBegining<T>(array:T[],value:T) {
let newArr = [value, ...array];
return newArr;
}