How it works
The explanation, with examples you can run.
any: no checking, and it spreads
declare const text: string const data = JSON.parse(text) // anyconst name = data.user.profile.name // any; nothing was checkedconst upper: string = name.toUpperCase() // accepted function shout(s: string) { return s.toUpperCase()}shout(data.count) // accepted, even if count is a numberany can be assigned to every type and every type can be assigned to it, and each operation on it produces another any. A single any from a library call or a quick fix can therefore travel through many functions, switching checks off wherever it goes, without an error anywhere. noImplicitAny (part of strict) stops the checker from silently choosing any when it can't infer a type, but explicit any, and any returned by APIs such as JSON.parse and response.json(), still get through.
unknown: you must check first
function length(value: unknown): number { // value.length here would fail: 'value' is of type 'unknown' if (typeof value === "string" || Array.isArray(value)) return value.length return 0}unknown accepts every value, like any, but allows nothing until the value has been narrowed. That makes it the right type for anything from outside your program: parsed JSON, a message from another window, a value from storage. With useUnknownInCatchVariables (also in strict), the error in a catch block is unknown, because JavaScript lets code throw anything, not only Error objects:
try { JSON.parse("{")} catch (err) { const message = err instanceof Error ? err.message : String(err) console.log(message)}never: no value at all
never turns up in three places:
- Code that can't be reached. After every member of a union has been handled, the remaining type is
never. Exhaustiveness checks rely on this. - Functions that never return. A function that always throws or loops forever can be declared
(): never. Code after a call to it is treated as unreachable, so it narrows like athrow. - Contradictions.
string & numberisnever, because no value is both. An unexpectedneverin an error message usually means two constraints conflict.
function fail(message: string): never { throw new Error(message)} function port(raw: string | undefined): number { if (raw === undefined) fail("PORT is not set") return Number(raw) // raw is string here: fail() never returns}A rule of thumb
If you don't know the type yet, use unknown and narrow it. If you know it but can't express it, write the type, or a cast at one well-commented spot. Keep any for migrating old code, and find the remaining uses with the @typescript-eslint/no-explicit-any lint rule or a search for : any.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.