Type systems were traditionally binary affairs: either a language had a fully static type system or a fully dynamic one. TypeScript blurs the line, because its type system is optional and gradual. You can add types to parts of your program but not others.
This is essential for migrating existing JavaScript codebases to TypeScript bit by bit (Chapter 8). Key to this is the any
type, which effectively disables type checking for parts of your code. It is both powerful and prone to abuse. Learning to use any
wisely is essential for writing effective TypeScript. This chapter walks you through how to limit the downsides of any
while still retaining its benefits.
function
processBar
(
b
:Bar
)
{
/* ... */
}
function
f() {
const
x
=
expressionReturningFoo
();
processBar
(
x
);
// ~ Argument of type 'Foo' is not assignable to
// parameter of type 'Bar'
}
If you somehow know from context that x
is assignable to Bar
in addition to Foo
, you can force TypeScript to accept this code in two ways:
function
f1() {
const
x
:any
=
expressionReturningFoo
();
// Don't do this
processBar
(
x
);
}
function
f2() {
const
x
=
expressionReturningFoo
();
processBar
(
x
as
any
);
// Prefer this
}
Of these, the second form is vastly preferable. Why? Because the any
type is scoped to a single expression in a function argument. It has no effect outside this argument or this line. If code after the processBar
call references x
, its type will still be Foo
, and it will still be able to trigger type errors, whereas in the first example its type is any
until it goes out of scope at the end of the function.
The stakes become significantly higher if you return x
from this function. Look what happens:
function
f1() {
const
x
:any
=
expressionReturningFoo
();
processBar
(
x
);
return
x
;
}
function
g() {
const
foo
=
f1
();
// Type is any
foo
.
fooMethod
();
// This call is unchecked!
}
An any
return type is “contagious” in that it can spread throughout a codebase. As a result of our changes to f
, an any
type has quietly appeared in g
. This would not have happened with the more narrowly scoped any
in f2
.
(This is a good reason to consider including explicit return type annotations, even when the return type can be inferred. It prevents an any
type from “escaping.” See discussion in Item 19.)
We used any
here to silence an error that we believed to be incorrect. Another way to do this is with @ts-ignore
:
function
f1() {
const
x
=
expressionReturningFoo
();
// @ts-ignore
processBar
(
x
);
return
x
;
}
This silences an error on the next line, leaving the type of x
unchanged. Try not to lean too heavily on @ts-ignore
: the type checker usually has a good reason to complain. It also means that if the error on the next line changes to something more problematic, you won’t know.
You may also run into situations where you get a type error for just one property in a larger object:
const
config
:Config
=
{
a
:1
,
b
:2
,
c
:
{
key
:value
// ~~~ Property ... missing in type 'Bar' but required in type 'Foo'
}
};
You can silence errors like this by throwing an as any
around the whole config
object:
const
config
:Config
=
{
a
:1
,
b
:2
,
c
:
{
key
:value
}
}
as
any
;
// Don't do this!
But this has the side effect of disabling type checking for the other properties (a
and b
) as well. Using a more narrowly scoped any
limits the damage:
const
config
:Config
=
{
a
:1
,
b
:2
,
// These properties are still checked
c
:
{
key
:value
as
any
}
};
Make your uses of any
as narrowly scoped as possible to avoid undesired loss of type safety elsewhere in your code.
Never return an any
type from a function. This will silently lead to the loss of type safety for any client calling the function.
Consider @ts-ignore
as an alternative to any
if you need to silence one error.
The any
type encompasses all values that can be expressed in JavaScript. This is a vast set! It includes not just all numbers and strings, but all arrays, objects, regular expressions, functions, classes, and DOM elements, not to mention null
and undefined
. When you use an any
type, ask whether you really had something more specific in mind. Would it be OK to pass in a regular expression or a function?
Often the answer is “no,” in which case you might be able to retain some type safety by using a more specific type:
function
getLengthBad
(
array
:any
)
{
// Don't do this!
return
array
.
length
;
}
function
getLength
(
array
:any
[])
{
return
array
.
length
;
}
The latter version, which uses any[]
instead of any
, is better in three ways:
The reference to array.length
in the function body is type checked.
The function’s return type is inferred as number
instead of any
.
Calls to getLength
will be checked to ensure that the parameter is an array:
getLengthBad
(
/123/
);
// No error, returns undefined
getLength
(
/123/
);
// ~~~~~ Argument of type 'RegExp' is not assignable
// to parameter of type 'any[]'
If you expect a parameter to be an array of arrays but don’t care about the type, you can use any[][]
. If you expect some sort of object but don’t know what the values will be, you can use {[key: string]: any}
:
function
hasTwelveLetterKey
(
o
:
{[
key
:string
]
:
any
})
{
for
(
const
key
in
o
)
{
if
(
key
.
length
===
12
)
{
return
true
;
}
}
return
false
;
}
You could also use the object
type in this situation, which includes all non-primitive types. This is slightly different in that, while you can still enumerate keys, you can’t access the values of any of them:
function
hasTwelveLetterKey
(
o
:object
)
{
for
(
const
key
in
o
)
{
if
(
key
.
length
===
12
)
{
console
.
log
(
key
,
o
[
key
]);
// ~~~~~~ Element implicitly has an 'any' type
// because type '{}' has no index signature
return
true
;
}
}
return
false
;
}
If this sort of type fits your needs, you might also be interested in the unknown
type. See Item 42.
Avoid using any
if you expect a function type. You have several options here depending on how specific you want to get:
type
Fn0
=
()
=>
any
;
// any function callable with no params
type
Fn1
=
(
arg
:any
)
=>
any
;
// With one param
type
FnN
=
(...
args
:any
[])
=>
any
;
// With any number of params
// same as "Function" type
All of these are more precise than any
and hence preferable to it. Note the use of any[]
as the type for the rest parameter in the last example. any
would also work here but would be less precise:
const
numArgsBad
=
(...
args
:any
)
=>
args
.
length
;
// Returns any
const
numArgsGood
=
(...
args
:any
[])
=>
args
.
length
;
// Returns number
This is perhaps the most common use of the any[]
type.
There are many functions whose type signatures are easy to write but whose implementations are quite difficult to write in type-safe code. And while writing type-safe implementations is a noble goal, it may not be worth the difficulty to deal with edge cases that you know don’t come up in your code. If a reasonable attempt at a type-safe implementation doesn’t work, use an unsafe type assertion hidden inside a function with the right type signature. Unsafe assertions hidden inside well-typed functions are much better than unsafe assertions scattered throughout your code.
Suppose you want to make a function cache its last call. This is a common technique for eliminating expensive function calls with frameworks like React.1 It would be nice to write a general cacheLast
wrapper that adds this behavior to any function. Its declaration is easy to write:
declare
function
cacheLast
<
T
extends
Function
>
(
fn
:T
)
:
T
;
Here’s an attempt at an implementation:
function
cacheLast
<
T
extends
Function
>
(
fn
:T
)
:
T
{
let
lastArgs
:any
[]
|
null
=
null
;
let
lastResult
:any
;
return
function
(...
args
:any
[])
{
// ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type '(...args: any[]) => any' is not assignable to type 'T'
if
(
!
lastArgs
||
!
shallowEqual
(
lastArgs
,
args
))
{
lastResult
=
fn
(...
args
);
lastArgs
=
args
;
}
return
lastResult
;
};
}
The error makes sense: TypeScript has no reason to believe that this very loose function has any relation to T
. But you know that the type system will enforce that it’s called with the right parameters and that its return value is given the correct type. So you shouldn’t expect too many problems if you add a type assertion here:
function
cacheLast
<
T
extends
Function
>
(
fn
:T
)
:
T
{
let
lastArgs
:any
[]
|
null
=
null
;
let
lastResult
:any
;
return
function
(...
args
:any
[])
{
if
(
!
lastArgs
||
!
shallowEqual
(
lastArgs
,
args
))
{
lastResult
=
fn
(...
args
);
lastArgs
=
args
;
}
return
lastResult
;
}
as
unknown
as
T
;
}
And indeed this will work great for any simple function you pass it. There are quite a few any
types hidden in this implementation, but you’ve kept them out of the type signature, so the code that calls cacheLast
will be none the wiser.
(Is this actually safe? There are a few real problems with this implementation: it doesn’t check that the values of this
for successive calls are the same. And if the original function had properties defined on it, then the wrapped function would not have these, so it wouldn’t have the same type. But if you know that these situations don’t come up in your code, this implementation is just fine. This function can be written in a type-safe way, but it is a more complex exercise that is left to the reader.)
The shallowEqual
function from the previous example operated on two arrays and is easy to type and implement. But the object variation is more interesting. As with cacheLast
, it’s easy to write its type signature:
declare
function
shallowObjectEqual
<
T
extends
object
>
(
a
:T
,
b
:T
)
:
boolean
;
The implementation requires some care since there’s no guarantee that a
and b
have the same keys (see Item 54):
function
shallowObjectEqual
<
T
extends
object
>
(
a
:T
,
b
:T
)
:
boolean
{
for
(
const
[
k
,
aVal
]
of
Object
.
entries
(
a
))
{
if
(
!
(
k
in
b
)
||
aVal
!==
b
[
k
])
{
// ~~~~ Element implicitly has an 'any' type
// because type '{}' has no index signature
return
false
;
}
}
return
Object
.
keys
(
a
).
length
===
Object
.
keys
(
b
).
length
;
}
It’s a bit surprising that TypeScript complains about the b[k]
access despite your having just checked that k in b
is true. But it does, so you have no choice but to cast:
function
shallowObjectEqual
<
T
extends
object
>
(
a
:T
,
b
:T
)
:
boolean
{
for
(
const
[
k
,
aVal
]
of
Object
.
entries
(
a
))
{
if
(
!
(
k
in
b
)
||
aVal
!==
(
b
as
any
)[
k
])
{
return
false
;
}
}
return
Object
.
keys
(
a
).
length
===
Object
.
keys
(
b
).
length
;
}
This type assertion is harmless (since you’ve checked k in b
), and you’re left with a correct function with a clear type signature. This is much preferable to scattering iteration and assertions to check for object equality throughout your code!
Sometimes unsafe type assertions are necessary or expedient. When you need to use one, hide it inside a function with a correct signature.
In TypeScript a variable’s type is generally determined when it is declared. After this, it can be refined (by checking if it is null
, for instance), but it cannot expand to include new values. There is one notable exception to this, however, involving any
types.
In JavaScript, you might write a function to generate a range of numbers like this:
function
range
(
start
,
limit
)
{
const
out
=
[];
for
(
let
i
=
start
;
i
<
limit
;
i
++
)
{
out
.
push
(
i
);
}
return
out
;
}
When you convert this to TypeScript, it works exactly as you’d expect:
function
range
(
start
:number
,
limit
:number
)
{
const
out
=
[];
for
(
let
i
=
start
;
i
<
limit
;
i
++
)
{
out
.
push
(
i
);
}
return
out
;
// Return type inferred as number[]
}
Upon closer inspection, however, it’s surprising that this works! How does TypeScript know that the type of out
is number[]
when it’s initialized as []
, which could be an array of any type?
Inspecting each of the three occurrences of out
to reveal its inferred type starts to tell the story:
function
range
(
start
:number
,
limit
:number
)
{
const
out
=
[];
// Type is any[]
for
(
let
i
=
start
;
i
<
limit
;
i
++
)
{
out
.
push
(
i
);
// Type of out is any[]
}
return
out
;
// Type is number[]
}
The type of out
starts as any[]
, an undifferentiated array. But as we push number
values onto it, its type “evolves” to become number[]
.
This is distinct from narrowing (Item 22). An array’s type can expand by pushing different elements onto it:
const
result
=
[];
// Type is any[]
result
.
push
(
'a'
);
result
// Type is string[]
result
.
push
(
1
);
result
// Type is (string | number)[]
With conditionals, the type can even vary across branches. Here we show the same behavior with a simple value, rather than an array:
let
val
;
// Type is any
if
(
Math
.
random
()
<
0.5
)
{
val
=
/hello/
;
val
// Type is RegExp
}
else
{
val
=
12
;
val
// Type is number
}
val
// Type is number | RegExp
A final case that triggers this “evolving any” behavior is if a variable is initially null
. This often comes up when you set a value in a try
/catch
block:
let
val
=
null
;
// Type is any
try
{
somethingDangerous
();
val
=
12
;
val
// Type is number
}
catch
(
e
)
{
console
.
warn
(
'alas!'
);
}
val
// Type is number | null
Interestingly, this behavior only happens when a variable’s type is implicitly any
with noImplicitAny
set! Adding an explicit any
keeps the type constant:
let
val
:any
;
// Type is any
if
(
Math
.
random
()
<
0.5
)
{
val
=
/hello/
;
val
// Type is any
}
else
{
val
=
12
;
val
// Type is any
}
val
// Type is any
This behavior can be confusing to follow in your editor since the type is only “evolved” after you assign or push an element. Inspecting the type on the line with the assignment will still show any
or any[]
.
If you use a value before any assignment to it, you’ll get an implicit any error:
function
range
(
start
:number
,
limit
:number
)
{
const
out
=
[];
// ~~~ Variable 'out' implicitly has type 'any[]' in some
// locations where its type cannot be determined
if
(
start
===
limit
)
{
return
out
;
// ~~~ Variable 'out' implicitly has an 'any[]' type
}
for
(
let
i
=
start
;
i
<
limit
;
i
++
)
{
out
.
push
(
i
);
}
return
out
;
}
Put another way, “evolving” any
types are only any
when you write to them. If you try to read from them while they’re still any
, you’ll get an error.
Implicit any
types do not evolve through function calls. The arrow function here trips up inference:
function
makeSquares
(
start
:number
,
limit
:number
)
{
const
out
=
[];
// ~~~ Variable 'out' implicitly has type 'any[]' in some locations
range
(
start
,
limit
).
forEach
(
i
=>
{
out
.
push
(
i
*
i
);
});
return
out
;
// ~~~ Variable 'out' implicitly has an 'any[]' type
}
In cases like this, you may want to consider using an array’s map
and filter
methods to build arrays in a single statement and avoid iteration and evolving any
entirely. See Items 23 and 27.
Evolving any
comes with all the usual caveats about type inference. Is the correct type for your array really (string|number)[]
? Or should it be number[]
and you incorrectly pushed a string
? You may still want to provide an explicit type annotation to get better error checking instead of using evolving any
.
Suppose you want to write a YAML parser (YAML can represent the same set of values as JSON but allows a superset of JSON’s syntax). What should the return type of your parseYAML
method be? It’s tempting to make it any
(like JSON.parse
):
function
parseYAML
(
yaml
:string
)
:
any
{
// ...
}
But this flies in the face of Item 38’s advice to avoid “contagious” any
types, specifically by not returning them from functions.
Ideally you’d like your users to immediately assign the result to another type:
interface
Book
{
name
:string
;
author
:string
;
}
const
book
:Book
=
parseYAML
(
`
name: Wuthering Heights
author: Emily Brontë
`
);
Without the type declarations, though, the book
variable would quietly get an any
type, thwarting type checking wherever it’s used:
const
book
=
parseYAML
(
`
name: Jane Eyre
author: Charlotte Brontë
`
);
alert
(
book
.
title
);
// No error, alerts "undefined" at runtime
book
(
'read'
);
// No error, throws "TypeError: book is not a
// function" at runtime
A safer alternative would be to have parseYAML
return an unknown
type:
function
safeParseYAML
(
yaml
:string
)
:
unknown
{
return
parseYAML
(
yaml
);
}
const
book
=
safeParseYAML
(
`
name: The Tenant of Wildfell Hall
author: Anne Brontë
`
);
alert
(
book
.
title
);
// ~~~~ Object is of type 'unknown'
book
(
"read"
);
// ~~~~~~~~~~ Object is of type 'unknown'
To understand the unknown
type, it helps to think about any
in terms of assignability. The power and danger of any
come from two properties:
Any type is assignable to the any
type.
The any
type is assignable to any other type.2
In the context of “thinking of types as sets of values” (Item 7), any
clearly doesn’t fit into the type system, since a set can’t simultaneously be both a subset and a superset of all other sets. This is the source of any
’s power but also the reason it’s problematic. Since the type checker is set-based, the use of any
effectively disables it.
The unknown
type is an alternative to any
that does fit into the type system. It has the first property (any type is assignable to unknown
) but not the second (unknown
is only assignable to unknown
and, of course, any
). The never
type is the opposite: it has the second property (can be assigned to any other type) but not the first (nothing can be assigned to never
).
Attempting to access a property on a value with the unknown
type is an error. So is attempting to call it or do arithmetic with it. You can’t do much with unknown
, which is exactly the point. The errors about an unknown
type will encourage you to add an appropriate type:
const
book
=
safeParseYAML
(
`
name: Villette
author: Charlotte Brontë
`
)
as
Book
;
alert
(
book
.
title
);
// ~~~~~ Property 'title' does not exist on type 'Book'
book
(
'read'
);
// ~~~~~~~~~ this expression is not callable
These errors are more sensible. Since unknown
is not assignable to other types, a type assertion is required. But it is also appropriate: we really do know more about the type of the resulting object than TypeScript does.
unknown
is appropriate whenever you know that there will be a value but you don’t know its type. The result of parseYAML
is one example, but there are others. In the GeoJSON spec, for example, the properties
property of a Feature is a grab-bag of anything JSON serializable. So unknown
makes sense:
interface
Feature
{
id?
:string
|
number
;
geometry
:Geometry
;
properties
:unknown
;
}
A type assertion isn’t the only way to recover a type from an unknown
object. An instanceof
check will do:
function
processValue
(
val
:unknown
)
{
if
(
val
instanceof
Date
)
{
val
// Type is Date
}
}
You can also use a user-defined type guard:
function
isBook
(
val
:unknown
)
:
val
is
Book
{
return
(
typeof
(
val
)
===
'object'
&&
val
!==
null
&&
'name'
in
val
&&
'author'
in
val
);
}
function
processValue
(
val
:unknown
)
{
if
(
isBook
(
val
))
{
val
;
// Type is Book
}
}
TypeScript requires quite a bit of proof to narrow an unknown
type: in order to avoid errors on the in
checks, you first have to demonstrate that val
is an object type and that it is non-null
(since typeof null === 'object'
).
You’ll sometimes see a generic parameter used instead of unknown
. You could have declared the safeParseYAML
function this way:
function
safeParseYAML
<
T
>
(
yaml
:string
)
:
T
{
return
parseYAML
(
yaml
);
}
This is generally considered bad style in TypeScript, however. It looks different than a type assertion, but is functionally the same. Better to just return unknown
and force your users to use an assertion or narrow to the type they want.
unknown
can also be used instead of any
in “double assertions”:
declare
const
foo
:Foo
;
let
barAny
=
foo
as
any
as
Bar
;
let
barUnk
=
foo
as
unknown
as
Bar
;
These are functionally equivalent, but the unknown
form has less risk if you do a refactor and break up the two assertions. In that case the any
could escape and spread. If the unknown
type escapes, it will probably just produce an error.
As a final note, you may see code that uses object
or {}
in a similar way to how unknown
has been described in this item. They are also broad types but are slightly narrower than unknown
:
The {}
type consists of all values except null
and undefined
.
The object
type consists of all non-primitive types. This doesn’t include true
or 12
or "foo"
but does include objects and arrays.
The use of {}
was more common before the unknown
type was introduced. Uses today are somewhat rare: only use {}
instead of unknown
if you really do know that null
and undefined
aren’t possibilities.
One of the most famous features of JavaScript is that its objects and classes are “open” in the sense that you can add arbitrary properties to them. This is occasionally used to create global variables on web pages by assigning to window
or document
:
window
.
monkey
=
'Tamarin'
;
document
.
monkey
=
'Howler'
;
or to attach data to DOM elements:
const
el
=
document
.
getElementById
(
'colobus'
);
el
.
home
=
'tree'
;
This style is particularly common with code that uses jQuery.
You can even attach properties to the prototypes of built-ins, with sometimes surprising results:
>
RegExp
.
prototype
.
monkey
=
'Capuchin'
"Capuchin"
>
/123/
.
monkey
"Capuchin"
These approaches are generally not good designs. When you attach data to window
or a DOM node, you are essentially turning it into a global variable. This makes it easy to inadvertently introduce dependencies between far-flung parts of your program and means that you have to think about side effects whenever you call a function.
Adding TypeScript introduces another problem: while the type checker knows about built-in properties of Document
and HTMLElement
, it certainly doesn’t know about the ones you’ve added:
document
.
monkey
=
'Tamarin'
;
// ~~~~~~ Property 'monkey' does not exist on type 'Document'
The most straightforward way to fix this error is with an any
assertion:
(
document
as
any
).
monkey
=
'Tamarin'
;
// OK
This satisfies the type checker, but, as should be no surprise by now, it has some downsides. As with any use of any
, you lose type safety and language services:
(
document
as
any
).
monky
=
'Tamarin'
;
// Also OK, misspelled
(
document
as
any
).
monkey
=
/Tamarin/
;
// Also OK, wrong type
The best solution is to move your data out of document
or the DOM. But if you can’t (perhaps you’re using a library that requires it or are in the process of migrating a JavaScript application), then you have a few next-best options available.
One is to use an augmentation, one of the special abilities of interface
(Item 13):
interface
Document
{
/** Genus or species of monkey patch */
monkey
:string
;
}
document
.
monkey
=
'Tamarin'
;
// OK
This is an improvement over using any
in a few ways:
You get type safety. The type checker will flag misspellings or assignments of the wrong type.
You can attach documentation to the property (Item 48).
You get autocomplete on the property.
There is a record of precisely what the monkey patch is.
In a module context (i.e., a TypeScript file that uses import
/ export
), you’ll need to add a declare global
to make this work:
export
{};
declare
global
{
interface
Document
{
/** Genus or species of monkey patch */
monkey
:string
;
}
}
document
.
monkey
=
'Tamarin'
;
// OK
The main issues with using an augmentation have to do with scope. First, the augmentation applies globally. You can’t hide it from other parts of your code or from libraries. And second, if you assign the property while your application is running, there’s no way to introduce the augmentation only after this has happened. This is particularly problematic when you patch HTML Elements, where some elements on the page will have the property and some will not. For this reason, you might want to declare the property to be string|undefined
. This is more accurate, but will make the type less convenient to work with.
Another approach is to use a more precise type assertion:
interface
MonkeyDocument
extends
Document
{
/** Genus or species of monkey patch */
monkey
:string
;
}
(
document
as
MonkeyDocument
).
monkey
=
'Macaque'
;
TypeScript is OK with the type assertion because Document
and MonkeyDocument
share properties (Item 9). And you get type safety in the assignment. The scope issues are also more manageable: there’s no global modification of the Document
type, just the introduction of a new type (which is only in scope if you import it). You have to write an assertion (or introduce a new variable) whenever you reference the monkey-patched property. But you can take that as encouragement to refactor into something more structured. Monkey patching shouldn’t be too easy!
Prefer structured code to storing data in globals or on the DOM.
If you must store data on built-in types, use one of the type-safe approaches (augmentation or asserting a custom interface).
Understand the scoping issues of augmentations.
Are you safe from the problems associated with any types once you’ve added type annotations for values with implicit any
types and enabled noImplicitAny
? The answer is “no”; any
types can still enter your program in two main ways:
any
typesEven if you follow the advice of Items 38 and 39, making your any
types both narrow and specific, they remain any
types. In particular, types like any[]
and {[key: string]: any}
become plain any
s once you index into them, and the resulting any
types can flow through your code.
This is particularly insidious since any
types from an @types
declaration file enter silently: even though you have noImplicitAny
enabled and you never typed any
, you still have any
types flowing through your code.
Because of the negative effects any
types can have on type safety and developer experience (Item 5), it’s a good idea to keep track of the number of them in your codebase. There are many ways to do this, including the type-coverage
package on npm:
$ npx type-coverage 9985 / 10117 98.69%
This means that, of the 10,117 symbols in this project, 9,985 (98.69%) had a type other than any
or an alias to any
. If a change inadvertently introduces an any
type and it flows through your code, you’ll see a corresponding drop in this percentage.
In some ways this percentage is a way of keeping score on how well you’ve followed the advice of the other items in this chapter. Using narrowly scoped any
will reduce the number of symbols with any
types, and so will using more specific forms like any[]
. Tracking this numerically helps you make sure things only get better over time.
Even collecting type coverage information once can be informative. Running type-coverage
with the --detail
flag will print where every any
type occurs in your code:
$ npx type-coverage --detail path/to/code.ts:1:10 getColumnInfo path/to/module.ts:7:1 pt2 ...
These are worth investigating because they’re likely to turn up sources of any
s that you hadn’t considered. Let’s look at a few examples.
Explicit any
types are often the result of choices you made for expediency earlier on. Perhaps you were getting a type error that you didn’t want to take the time to sort out. Or maybe the type was one that you hadn’t written out yet. Or you might have just been in a rush.
Type assertions with any
can prevent types from flowing where they otherwise would. Perhaps you’ve built an application that works with tabular data and needed a single-parameter function that built up some kind of column description:
function
getColumnInfo
(
name
:string
)
:
any
{
return
utils
.
buildColumnInfo
(
appState
.
dataSchema
,
name
);
// Returns any
}
The utils.buildColumnInfo
function returned any
at some point. As a reminder, you added a comment and an explicit “: any” annotation to the function.
However, in the intervening months you’ve also added a type for ColumnInfo
, and utils.buildColumnInfo
no longer returns any
. The any
annotation is now throwing away valuable type information. Get rid of it!
Third-party any
types can come in a few forms, but the most extreme is when you give an entire module an any
type:
declare
module
'my-module'
;
Now you can import anything from my-module
without error. These symbols all have any
types and will lead to more any
types if you pass values through them:
import
{
someMethod
,
someSymbol
}
from
'my-module'
;
// OK
const
pt1
=
{
x
:1
,
y
:2
,
};
// type is {x: number, y: number}
const
pt2
=
someMethod
(
pt1
,
someSymbol
);
// OK, pt2's type is any
Since the usage looks identical to a well-typed module, it’s easy to forget that you stubbed out the module. Or maybe a coworker did it and you never knew in the first place. It’s worth revisiting these from time to time. Maybe there are official type declarations for the module. Or perhaps you’ve gained enough understanding of the module to write types yourself and contribute them back to the community.
Another common source of any
s with third-party declarations is when there’s a bug in the types. Maybe the declarations didn’t follow the advice of Item 29 and declared a function to return a union type when in fact it returns something much more specific. When you first used the function this didn’t seem worth fixing so you used an any
assertion. But maybe the declarations have been fixed since then. Or maybe it’s time to fix them yourself!
The considerations that led you to use an any
type might not apply any more. Maybe there’s a type you can plug in now where previously you used any
. Maybe an unsafe type assertion is no longer necessary. Maybe the bug in the type declarations you were working around has been fixed. Tracking your type coverage highlights these choices and encourages you to keep revisiting them.
Even with noImplicitAny
set, any
types can make their way into your code either through explicit any
s or third-party type declarations (@types
).
Consider tracking how well-typed your program is. This will encourage you to revisit decisions about using any
and increase type safety over time.
3.135.246.193