Wstęp
Jestem zawodowo programistą od 9 lat i przez ten czas pracowałem w różnych technologiach i pisałem bardzo różne rzeczy - od urządzeń wbudowanych, automotive i technologii mobilnych po technologie webowe, z którymi pracuję aktualnie. Przez te lata zauważyłem, że podejście w zależności od obszaru jest zupełnie inne, ale też takie szerokie znajomości pomagają w pracy.
TypeScript
Pisałem w wielu językach programowania. Zaczynałem od C i C++ w podstawówce, potem w technikum próbowałem sił również w perlu. Na studiach była Java, ale kilka projektów zrobiłem w C++. Pisałem też wtedy swoje pierwsze projekty w JavaScript. W pracy doszedł potem Python, a po drodze liznąłem jeszcze kilku języków. Lecz jedno jest pewne. Zawsze w pythonie brakowało mi typowania z javy i C++, a w C++ brakowało mi dynamicznych template (takich na poziomie runtime) z Javy. W Javie denerwowała zaś mnie wydajność. Każdy język ma swoje wady i zalety. Największą wadą i zaletą JS w mojej opinii były typy. Dlaczego wadą i zaletą? Bo czasem fajnie było napisać stronę w leaflet i rozszerzyć pewne obiekty używając prototype
. Był to dla mnie taki wytrych, który pozwalał na bardzo dużo. Z drugiej strony, czasem zależało mi abym miał wygodne podpowiadanie propsów w obiekcie i aby one tam faktycznie były. TypeScript to pod tym względem najlepszy język bo pozwala zachować elastyczność i dodać do tej elastyczności pewne reguły.
Dodatkowo nawet najbardziej typowany język nie pozwala tak dobrze typować jak Typescript. Przykład:
type PowerStatus = 'on' | 'off';
const variable: PowerStatus = 'on';
const variable2: PowerStatus = 'string'; // Type '"string"' is not assignable to type 'PowerStatus'
Od roku jestem w projekcie, który używa Nest.js i TypeScript oraz kilku innych fajnych technologii, o których może napiszę kiedyś indziej. Dzisiaj skupię się na TS, który nie tak dawno dorobił się wydania 4.1. Co dla mnie jest osobiście ważne, że to pierwsze wydanie, którego używałem zanim pojawiło się w wersji stabilnej, a zmiany, które przynosi były mi znane jeszcze przed latem.
Template Literal Types to w mojej opinii najlepszy feature, który się pojawił bo nigdy jeszcze nie spędziłem tyle czasu na zabawę typami. Daje on ogromne możliwości. Wyobraźcie sobie, że macie funkcję, która przerabia obiekt tak, że np.:
{
propercja_1: true,
propercja_tekstowa: string,
}
zostaje zamieniona na:
{
propercja1: true,
propercjaTekstowa: string,
}
sprawa jest oczywiście bardzo prosta, gdy znamy dokładny model i w return type możemy podać inny model. Co jednak jeśli ta funkcja ma być generyczna?
Możemy wtedy napisać typ, który podmieni nam jakis_tekst
na jakisTekst
w ten sposób:
type Rename<
S extends string
> = S extends ${infer PRE}_${infer POST}
? Rename<${PRE}${Capitalize<POST>}>
: S;
A następnie użyć kolejnej nowej rzeczy w języku czyli mapowania kluczy (słowo kluczowe as
):
type ReplaceProps<O extends object> = {
[P in keyof O & string as Rename<P>]: O[P];
};
type Test = {
jakis_typ: number;
jakis_string: string;
}
const w: ReplaceProps<Test> ={ jakisTyp: 3, jakisString: 'a'};
const z: ReplaceProps<Test> ={ jakisTyp: 3, jakis_string: 'a'};
// Type '{ jakisTyp: number; jakis_string: string; }' is not assignable to type
// ReplaceProps<Test>'. Object literal may only specify known properties, but 'jakis_string'
// does not exist in type 'ReplaceProps<Test>'. Did you mean to write 'jakisString'?
Fajne prawda? Zrobiłem już tyle różnych własnych typów używając tego, że mam wrażenie, że za jakiś czas nie będę potrafił żyć bez tego. Możliwość podawania {TYP}
na poziomie typów to prawdziwy As w rękawie.
Od pewnego czasu pracuję w weekendy nad własną nakładką na infrastrukturę. Coś podobnego do ORM, ale jedynie do odczytu, ale za to zawiera różne dodatkowe funkcje.
Podrzucę kilka rozwiązań:
- Sprawdzanie czy string na wejściu pasuje pod {field} as {name}
- Wyciąganie modelu, w sposób zagnieżdżony
export class ResultBuilder<T extends object> {
public select(list: SelectorKeys<T> | SelectorKeys<T>[]) {}
public filter<K extends keyof Filter<T>, L extends Filter<T>[K]>(
list: K,
...args: Parameters<L>
) {}
}
//-------------------------------------------------------------------
type AEntity = {
a: number;
b: string;
differentMethod(active: boolean, end: number): void;
};
type BEntity = {
aEntity: AEntity;
testProperty: number;
isActive(active: boolean): void;
};
const rb = new ResultBuilder<BEntity>();
rb.select('testProperty');
rb.select('aEntity.a');
rb.select('aEntity.as'); // Failed because of missing property
rb.filter('isActive', true);
rb.filter('isActive', 42); // Failed because of wrong filter type
rb.filter('aEntity.differentMethod', true); // Failed because of only one param
rb.filter('aEntity.differentMethod', true, 42);
Jest jeszcze wiele rzeczy, które próbowałem pisać. Ale zostawię sobie te rzeczy na później ;-)
Miłego programowania!