Menu

typeof w TypeScript: type guardy i operator typu typeof

typeof ma w TypeScript dwa zadania. W kodzie to operator JavaScriptu, który w czasie wykonania zwraca "string", "number", "object" i tak dalej, a TypeScript na jego podstawie zawęża typy. W typie to zapytanie o typ, które kopiuje typ zmiennej, jak w keyof typeof obj i ReturnType<typeof fn>.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

typeof oznacza w TypeScript dwie rzeczy. W wyrażeniu to operator JavaScriptu: wykonuje się, zwraca string taki jak "string" albo "number", a TypeScript zawęża zmienną, gdy porównujesz ten string. W pozycji typu to zapytanie o typ: typeof config daje statyczny typ zmiennej config.

Ostatnia linia pokazuje różnicę: typeof custom w console.log to operator czasu wykonania i wypisuje object, a typeof defaults po type Options = to zapytanie w czasie kompilacji, które nigdy nie trafia do JavaScriptu.

Dwa znaczenia w skrócie

typeof w czasie wykonania (JavaScript)Zapytanie o typ typeof (TypeScript)
Gdzie się pojawiaW dowolnym wyrażeniu: if, return, console.log(...)W pozycjach typu: po :, w type X = ..., wewnątrz <...>
Przykładtypeof x === "string"let y: typeof x;
ObliczaneGdy kod się wykonujePrzez kompilator, a potem usuwane
WynikJeden z ośmiu stringówTyp
Do czegoSprawdzanie wartości i zawężanie jej typuWyprowadzanie typu z wartości

TypeScript rozróżnia je po pozycji, więc to samo słowo kluczowe nigdy nie oznacza obu rzeczy naraz.

Wyniki typeof w czasie wykonania

Operator czasu wykonania zwraca jeden z ośmiu stringów. Uruchom to, żeby zobaczyć, co dają typowe wartości:

WartośćWynik typeofUwaga
"hi", template stringi"string"
42, 3.14, NaN, Infinity"number"NaN to liczba, sprawdzaj go przez Number.isNaN
10n"bigint"
true, false"boolean"
undefined"undefined"
Symbol("id")"symbol"
funkcje, funkcje strzałkowe, klasy"function"klasa to funkcja konstruktora
null"object"historyczny błąd JavaScriptu
{}, [], new Date(), new Map()"object"tablice to obiekty: użyj Array.isArray
new String("x"), new Number(1)"object"obiekty opakowujące, unikaj ich

W zwykłym JavaScripcie typeof na zmiennej, której nigdy nie zadeklarowano, zwraca "undefined" zamiast rzucać wyjątek. W TypeScript taki kod się nie kompiluje: Cannot find name 'notDeclared'. (TS2304).

Zawężanie przez typeof

Porównanie typeof w if, switch, operatorze trójargumentowym albo po && zawęża zmienną w każdej gałęzi. To standardowy sposób obsługi unii typów prostych i pierwsze sprawdzenie, po które warto sięgnąć przy unknown.

typeof x !== "string" zawęża w drugą stronę, do wszystkiego poza string. Kompilator zna osiem możliwych stringów, więc literówka w porównaniu to błąd kompilacji, a nie sprawdzenie, które po cichu zawsze daje false:

Kompilator wypisuje index.ts(3,7): error TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"strnig"' have no overlap. W zwykłym JavaScripcie ta literówka po cichu wysłałaby każdy string ścieżką dla number. Popraw pisownię, a kod wypisze text.

Pułapka typeof null

Ponieważ typeof null to "object", TypeScript zawęża typeof x === "object" do object | null, a nie do object. Dodaj sprawdzenie null, bo inaczej odczyt właściwości jest błędem (TS18047 'x' is possibly 'null'.).

typeof nie odróżni tablic, dat, map i zwykłych obiektów: wszystkie to "object". Dla tablic używaj Array.isArray(x), a dla instancji klas x instanceof Date. Ani typeof, ani nic innego nie sprawdzi w czasie wykonania, czy wartość pasuje do interfejsu, bo interfejsy są usuwane przy kompilacji. Do tego napisz funkcję type guard, która sprawdza właściwości.

typeof w pozycjach typu

W typie typeof someVariable kopiuje typ, który TypeScript wywnioskował (albo który zadeklarowano) dla tej zmiennej. Oszczędza ręcznego pisania typu dla wartości, która już istnieje, i utrzymuje oba w zgodzie.

const theme = {
  primary: "#3178c6",
  spacing: 8,
  dark: false,
};

type Theme = typeof theme;
// { primary: string; spacing: number; dark: boolean }

function withSpacing(t: Theme, factor: number): Theme {
  return { ...t, spacing: t.spacing * factor };
}

console.log(withSpacing(theme, 2)); // { primary: '#3178c6', spacing: 16, dark: false }

let userName = "Ada";
const fixed = "Ada";
type A = typeof userName; // string
type B = typeof fixed;    // "Ada" (a const keeps its literal type)

Wynik zależy od tego, jak zadeklarowano zmienną: let albo właściwość obiektu są poszerzane (string, number), a typ prosty w const zachowuje typ literału. Dodaj as const do obiektu albo tablicy, a typeof da typy literałów readonly na każdym poziomie. typeof przyjmuje też dostęp do właściwości, typeof theme.spacing to number, ale nie dowolne wyrażenia: type T = typeof getTheme(); to błąd składni (';' expected., TS1005). Dla typu zwracanego przez wywołanie użyj ReturnType<typeof getTheme>.

keyof typeof: klucze obiektu jako typ

keyof typeof obj to najczęstsze połączenie. typeof zamienia obiekt w typ, keyof bierze jego klucze, a ty dostajesz unię nazw właściwości, która pozostaje poprawna, gdy obiekt się zmienia.

Ta sama sztuczka na stałej tablicy daje unię jej wartości: przy const roles = ["admin", "editor"] as const typ (typeof roles)[number] to "admin" | "editor". Bez as const byłby to tylko string. Sam keyof omawia strona o keyof.

ReturnType<typeof fn> i typeof na klasach

Typy narzędziowe, takie jak ReturnType i Parameters, przyjmują typ funkcji. Nazwa funkcji to wartość, więc najpierw przepuść ją przez typeof:

Zapis ReturnType<createUser> kończy się błędem 'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'? (TS2749), czyli kompilator prosi dokładnie o to.

Klasy to jedyny przypadek, gdy nazwa jest jednocześnie wartością i typem. User jako typ oznacza instancję klasy, a typeof User oznacza samą klasę, czyli konstruktor razem z jego statycznymi składowymi:

class Point {
  static origin = new Point(0, 0);
  constructor(public x: number, public y: number) {}
}

const p: Point = new Point(1, 2);        // an instance
const Ctor: typeof Point = Point;        // the class (constructor + statics)
const q = new Ctor(3, 4);                // q: Point
type Instance = InstanceType<typeof Point>; // Point

typeof, instanceof i type guard

SprawdzenieDziała naPrzykład
typeoftypy proste, funkcje, "czy to obiekt"typeof x === "number"
Array.isArraytabliceArray.isArray(x)
instanceofinstancje klas (Date, Error, twoje klasy)x instanceof Date
inunie obiektów, według właściwości"email" in x
Funkcja type guardinterfejsy, aliasy typów, cała resztaisUser(x) z x is User

Wybierz najwęższe narzędzie, które pasuje. Dla instancji klas zobacz instanceof. Dla kształtów opisanych interfejsem jedynym dostępnym sprawdzeniem w czasie wykonania jest funkcja type guard.

Najczęściej zadawane pytania

Co robi typeof w TypeScript?

Dwie różne rzeczy zależnie od miejsca. W wyrażeniu typeof x to operator JavaScriptu: wykonuje się i zwraca string, taki jak "string" albo "object", a TypeScript zawęża x, gdy porównujesz ten string. W adnotacji typu typeof x to zapytanie o typ: oblicza je kompilator, daje statyczny typ zmiennej x i znika z wyemitowanego JavaScriptu.

Czym jest keyof typeof w TypeScript?

keyof typeof obj daje unię nazw właściwości obiektu jako typy literałów string. typeof obj zamienia wartość obiektu w jej typ, a keyof bierze klucze tego typu. Dla const colors = { red: "#f00", blue: "#00f" } typ keyof typeof colors to "red" | "blue".

Dlaczego typeof null zwraca "object"?

To błąd z pierwszej wersji JavaScriptu, którego nie da się już naprawić bez psucia stron internetowych. TypeScript go odwzorowuje: po typeof x === "object" zmienna x jest zawężona do object | null, więc przed odczytem właściwości trzeba też sprawdzić x !== null.

Jak sprawdzić, czy wartość jest tablicą w TypeScript?

Użyj Array.isArray(value), co zawęża do typu tablicy. typeof tego nie potrafi: typeof [] to "object", tak samo jak dla zwykłych obiektów i null.

Czym różni się typeof od instanceof w TypeScript?

typeof sprawdza podstawową kategorię wartości ("string", "number", "function", "object"...). instanceof sprawdza, czy obiekt został utworzony przez konkretną klasę albo konstruktor, na przykład Date albo twoją class User. Używaj typeof dla typów prostych, a instanceof dla instancji klas. Żaden z nich nie sprawdzi interfejsu ani aliasu typu, bo te nie istnieją w czasie wykonania.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ