Maks Smagin
Maks SmaginLead Software Engineer

Typescript - 3 pytania z rozmowy kwalifikacyjnej

Poznaj pytania z TypeScript, które możesz usłyszeć na rozmowie kwalifikacyjnej. Są na trzech różnych poziomach znajomości TS.
26.06.20235 min
Typescript - 3 pytania z rozmowy kwalifikacyjnej

W ciągu ostatnich kilku lat przeprowadziłem wiele rozmów kwalifikacyjnych na stanowisko Senior NodeJS Developera, a jednym z kluczowych wymagań zawsze była dobra znajomość języka TypeScript. Dokładnie jak dobra? Cóż, przez „dobra” rozumiem zdolność programisty do używania tego języka nie tylko jako zestawu słów kluczowych, których można używać do definiowania typów, ale jako narzędzia, które pomaga projektować i pisać lepszy kod.

Co ciekawe, wielu kandydatów z wieloletnim doświadczeniem zmagało się z podstawowymi pytaniami dotyczącymi języka TypeScript i typów w ogóle, ale wierzyli, że są w tym dobrzy. Postanowiłem więc napisać o tym krótki artykuł, aby objąć różne „poziomy” znajomości języka TypeScript, z zamiarem podzielenia się niektórymi z moich doświadczeń i pomocy innym programistom, aby byli lepsi w tym, co robią :)

1. Jaki jest typ A i dlaczego?

Zazwyczaj pytam kandydata o to samo, ponieważ ten fragment kodu jest naprawdę prosty, jednak uważam, że odpowiedź na to pytanie może dać nam wiele informacji na temat wiedzy kandydata na konkretny temat.

type A = number & string;


Odpowiedź jest naprawdę prosta, typ A to never, a dlaczego, ponieważ & jest operatorem przecięcia, a jeśli pomyślimy o stringu i liczbie jako zbiorach wartości, które można przypisać, to przecięciem tych zbiorów jest zbiór pusty, który w Typescript jest reprezentowany przez typ never, lub innymi słowy nie istnieje żadna wartość, która może należeć do liczby i stringa w tym samym czasie:

Ale wiele razy słyszałem u ludzi zdezorientowanie i mówili coś w stylu:

  • pojawi się błąd TypeScript, nie można utworzyć typu w ten sposób
  • typ A - jest stringiem i liczbą, więc można przypisać string i liczbę do A (tak jakby była to unia string|number)
  • ... itd


Myślę, że nie chodzi tylko o nieznajomość TypeScriptu, ale o brak ogólnego zrozumienia tego, jak działają typy, a jest to naprawdę ważna rzecz, ponieważ dosłownie opiera się na wszystkim innym, a jeśli będziesz wiedział o co chodzi, to inne rzeczy będą naprawdę łatwe do nauczenia.

Jeśli macie ten sam problem, polecam Wam przeczytać ten artykuł, który doskonale to wyjaśnia: https://ivov.dev/notes/typescript-and-set-theory

2. Walidacja w czasie wykonywania

Załóżmy więc, że nasz kandydat pomyślnie odpowiedział na pierwsze pytanie, więc dla mnie oznaczałoby to, że kandydat dobrze zrozumiał podstawy. Przejdźmy teraz do nieco bardziej złożonego i tym razem naprawdę praktycznego podejścia. Załóżmy, że mamy API, które zwraca nam string JSON zawierający informacje o użytkowniku.

Pytanie brzmi: Jak możemy zweryfikować obiekt użytkownika i upewnić się, że faktycznie jest on zgodny z typem User?

type User = {
  name: string;
  age: number;
};

const data = `{"name":"Bob","age":30}`;
const user: User = JSON.parse(data);

console.log(`Username: ${user.name.toLowerCase()}`);
console.log(`Age: ${user.age.toFixed(0)}`);


Odpowiedź jest zasadniczo dwutorowa:

Po pierwsze, jeśli typ User jest mały, tak jak teraz, z tylko dwoma polami i jest to dla nas w porządku, możemy napisać prostą funkcję walidacyjną:

function isUser(obj: unknown): obj is User {
  return (
    typeof obj['name'] === 'string' && 
    typeof obj['age'] === 'number'
  );
}


i użyć jej w ten sposób:

const data = `{"name":"Bob","age":30}`;
const user = JSON.parse(data); // user type if any here

if (isUser(user)) {
  console.log(`Username: ${user.name.toLowerCase()}`);
  console.log(`Age: ${user.age.toFixed(0)}`);
}


Drugi wariant, który jest nieco wygodniejszy, czyli użycie walidacji schematu przy użyciu dowolnej biblioteki: class-validator, zod, runtypes, joi, ... itd:

import Joi from 'joi';

const UserSchema = Joi.object({
  name: Joi.string(),
  age: Joi.number(),
});

const data = `{"name":"Bob","age":30}`;
const userData = JSON.parse(data)

try {
  const user = UserSchema.validate(userData);

  console.log(`Username: ${user.name.toLowerCase()}`);
  console.log(`Age: ${user.age.toFixed(0)}`);
} catch(e) {}


Zaskakująco wielu kandydatów odpowiedziałoby w ten sposób:

  • JSON.parse(data) jako User wystarczy
  • const user: User = JSON.parse(data) wystarczy
  • lub even if (typeof user === User) { …. }


To pytanie ma na celu sprawdzenie nie tylko wiedzy na temat walidacji danych, ale także upewnienie się, jak dobrze kandydat zna stos technologiczny, co w jaki sposób można zrobić itp.

3. Używanie rekurencji w typach

Ostatnie pytanie to zazwyczaj pytanie praktyczne i dotyczy tego, jak napisać typ rekurencyjny. W tym przykładzie kandydat musi zaproponować rozwiązanie problemu: załóżmy, że piszę funkcję find() do wyszukiwania użytkowników w bazie danych. Problem polega na tym, że typ User ma właściwości takie jak adres, który również jest obiektem. Ja chciałbym zbudować funkcję find() w taki sposób, aby móc wyszukiwać również po zagnieżdżonych właściwościach User, z pełną obsługą typów:

function find<T>(criteria: ...): T[] {
  ...
}

type User = {
  id: number;
  name: string;
  address: {
    country: string;
    city: string;
    house: string;
    zipcode: string;
  };
};

// in this example im searching by country only, even if 
// address has other properties.

const users = find({
  address: { coutry: 'UK' }
});


Jak możemy to zaimplementować: stwórzmy kolejny typ o nazwie DeepPartial i użyjmy go w funkcji find(), dla argumentu criteria:

type DeepPartial<T> = {
  [P in keyof T]?: DeepPartial<T[P]>;
};

function find<T>(criteria: DeepPartial<T>): T[] {
  ...
}

type User = {
  id: number;
  name: string;
  address: {
    country: string;
    city: string;
    house: string;
    zipcode: string;
  };
};

const users = find({
  address: { coutry: 'UK' }
});


DeepPartial<T> działa prawie tak samo jak Partial<T>, ale jedyną różnicą jest to, że stosuje się rekurencyjnie do każdej zagnieżdżonej właściwości obiektu:

type User = {
  id: number;
  name: string;
  address: {
    country: string;
    city: string;
    house: string;
    zipcode: string;
  };
};

// DeepPartial<User>
type DeepPartiallUser = {
  id?: number;
  name?: string;
  address?: {
    country?: string;
    city?: string;
    house?: string;
    zipcode?: string;
  };
};


To pytanie ma na celu sprawdzenie, jak dobrze kandydat radzi sobie z TypeScriptem w praktyce, w zakresie projektowania oprogramowania lub architektury, ponieważ obejmuje wiele tematów jednocześnie i zmusza kandydata do zastanowienia się, jak rozwiązać dane problemy we właściwy sposób.

Podsumowanie

Mam nadzieję, że te małe przykłady użycia TypeScriptu pomogą Ci w poszerzeniu umiejętności i w pisaniu lepszego kodu, chyba, że już je kojarzysz. Dzięki :)



Oryginał tekstu w języku angielskim przeczytasz tutaj.

<p>Loading...</p>