any i unknown przyjmują każdą wartość. Różnica polega na tym, co możesz z tą wartością potem zrobić: any pozwala na wszystko i niczego nie sprawdza, a unknown nie pozwala prawie na nic, dopóki nie udowodnisz, czym jest wartość.
Bez linii @ts-expect-error wywołanie u.toUpperCase() to błąd kompilacji TS18046. Sprawdzenie typeof zawęża u do string i wewnątrz tego bloku dostępna jest każda metoda napisów.
any vs unknown w skrócie
any | unknown | |
|---|---|---|
| Przyjmuje dowolną wartość | tak | tak |
Przypisanie do string, number, ... | tak, bez sprawdzania | nie (TS2322) |
| Odczyt właściwości, wywołanie metody | tak, bez sprawdzania | nie (TS18046) |
| Wywołanie jako funkcji | tak | nie |
Arytmetyka i porównania (x * 2, x + 1, x < 5) | tak | nie (TS18046) |
| Wymaga sprawdzenia przed użyciem | nie | tak (typeof, instanceof, in, strażnik typu) |
| Wpływ na sprawdzanie typów | wyłączone dla tej wartości i wszystkiego, czego dotknie | pozostaje włączone |
W terminologii teorii typów unknown to typ górny: każdy typ da się do niego przypisać, a on sam da się przypisać tylko do unknown i any. any to furtka awaryjna przypisywalna w obie strony, do wszystkiego poza never.
any wyłącza sprawdzanie typów
Wartości typu any kompilator ufa na ślepo. Akceptuje literówki, złe typy i brakujące właściwości, a błędy wychodzą dopiero w czasie działania.
Wynik pokazuje problem: zmienna z adnotacją number przechowuje napis, a ostatnia linia rzuca Cannot read properties of undefined (reading 'city'). any też się rozprzestrzenia. user.name jest typu any, więc każda wartość z niego obliczona jest any, a jedna nieotypowana wartość może wyłączyć sprawdzanie daleko od miejsca, w którym się pojawiła.
unknown każe najpierw sprawdzić
Przy unknown kompilator odrzuca każdą operację, dopóki kod nie zawęzi wartości. Zawężanie używa zwykłych sprawdzeń JavaScriptu, a w każdej gałęzi wartość ma sprawdzony typ.
Sprawdzenie value === null musi być przed sprawdzeniem obiektu, bo typeof null to "object". Po "id" in value TypeScript wie, że obiekt ma właściwość id typu unknown, bo nic nie mówi, co ona zawiera. String() jawnie zamienia ją na tekst; żeby użyć jej jako liczby, trzeba ją najpierw sprawdzić przez typeof.
Możesz też pominąć sprawdzenie asercją value as string i kompilator ją zaakceptuje. To obietnica bez żadnego sprawdzenia w czasie działania, więc lepiej wybierz prawdziwe sprawdzenie; zobacz asercje typów.
Walidacja JSON z unknown
JSON.parse jest zadeklarowane jako zwracające any, więc jego wynik po cichu wyłącza sprawdzanie. Dodaj do wyniku adnotację unknown i napisz strażnika typu, który sprawdzi kształt, zanim reszta kodu mu zaufa.
Pierwsze wejście wypisuje dark at 14px, drugie zostaje odrzucone, bo brakuje fontSize. Z any drugie wejście przeszłoby dalej jako Settings z niezdefiniowanym fontSize. Przy dużych schematach tę samą pracę wykonuje biblioteka walidująca, która przy okazji wyprowadza typ.
noImplicitAny
any bierze się nie tylko stąd, że ktoś go napisał. Parametr bez adnotacji i bez kontekstu, z którego można wywnioskować typ, też byłby any. Opcja noImplicitAny, włączana przez strict, zgłasza to jako błąd:
index.ts(2,17): error TS7006: Parameter 'x' implicitly has an 'any' type.
Poprawka to adnotacja, function double(x: number). Jawne napisanie x: any też się kompiluje i o to właśnie chodzi: any pozostaje widoczne w kodzie, więc można je wyszukać i przejrzeć.
Gdzie any nadal się wkrada
Nawet przy strict poniższe przypadki dają any, choć to słowo nie pojawia się w twoim kodzie:
| Źródło | Co dostajesz | Co zrobić |
|---|---|---|
JSON.parse(text) | any | adnotacja unknown, potem walidacja |
response.json() z typami przeglądarki (DOM) | Promise<any> | to samo co przy JSON.parse (własne typy fetch w Node zwracają już Promise<unknown>) |
| Pakiet bez definicji typów | any dla jego importów (z błędem, jeśli nie dodasz deklaracji) | zainstaluj @types/... albo napisz .d.ts |
value as any | any | użyj strażnika typu albo precyzyjnej asercji |
catch (e) z wyłączonym useUnknownInCatchVariables | any | strict zmienia to na unknown; zostaw tak |
Zmienna w catch jest typu unknown przy strict, bo rzucić można cokolwiek, nie tylko obiekty Error. Zawęź ją przez e instanceof Error, zanim odczytasz e.message.
Kiedy any jest dopuszczalne
any nie jest zakazane, ale każde użycie to miejsce, którego kompilator już nie chroni. Rozsądne zastosowania:
- Migracja projektu JavaScript, gdzie
anyoznacza to, co nie ma jeszcze typów. - Kod, którego system typów nie potrafi dobrze wyrazić, trzymany w małym zakresie za otypowaną sygnaturą funkcji.
- Kod testów, który celowo przekazuje złe dane.
We wszystkich innych przypadkach unknown obsługuje tę samą sytuację „nie znam tego typu”, zachowując sprawdzanie. Wiele zespołów wymusza to regułą lintera @typescript-eslint/no-explicit-any. Pokrewny typ, Record<string, unknown>, to zwykły wybór dla „jakiegoś obiektu o nieznanych wartościach”.
Najczęściej zadawane pytania
Czym różni się any od unknown w TypeScript?
Oba przyjmują dowolną wartość. Z any możesz zrobić z wartością cokolwiek (odczytać właściwości, wywołać ją, przypisać do number), a kompilator niczego nie sprawdza. Z unknown nie możesz zrobić prawie nic, dopóki nie zawęzisz typu sprawdzeniem w rodzaju typeof x === "string". unknown zostawia sprawdzanie typów włączone i dlatego jest bezpieczniejszym wyborem.
Kiedy używać unknown zamiast any?
Zawsze, gdy typ wartości nie jest znany w czasie kompilacji: sparsowany JSON, dane z odpowiedzi sieciowej, przechwycony błąd, wejście funkcji walidującej. Otypuj ją jako unknown i zawęź. Po any sięgaj tylko przy krótkotrwałej migracji albo w kodzie, którego system typów nie potrafi opisać.
Co oznacza "Object is of type 'unknown'"?
Błędy TS18046 ('x' is of type 'unknown') i TS2571 (Object is of type 'unknown', zgłaszany, gdy wartość nie jest zwykłą nazwą, na przykład load().id) oznaczają, że użyto wartości unknown tak, jakby miała konkretny typ, na przykład odczytując właściwość albo wywołując metodę. Najpierw sprawdź typ (typeof, instanceof, Array.isArray, in albo funkcja strażnika typu) i używaj wartości wewnątrz zawężonej gałęzi.
Dlaczego JSON.parse zwraca any?
Jego deklaracja w bibliotece standardowej to parse(text: string, ...): any, bo kompilator nie może wiedzieć, co zawiera tekst. Wynik po cichu wyłącza sprawdzanie wszystkiego, czego dotknie. Zamiast tego dodaj adnotację: const data: unknown = JSON.parse(text), a potem zwaliduj dane przed użyciem.
Czym jest noImplicitAny?
To opcja kompilatora, część strict, która zgłasza błąd, gdy deklaracja po cichu dostałaby typ any, bo nie ma adnotacji ani niczego, z czego można wywnioskować typ: TS7006 dla parametru, TS7005 lub TS7034 dla zmiennej, której typu nie da się ustalić. Dzięki niej any nie pojawia się bez tego, że ktoś go napisał. Jawne any jest nadal dozwolone.