We have different types of “named things” in TypeScript, including values and functions (and occasionally things that can be used as both). By the end of this chapter, you’ll have a solid understanding of how to examine and understand these entities in TypeScript.
In order to truly understand how types and values “stack” on each other, we’ll first tackle the concept of declaration merging. Often when people grasp how TypeScript handles this, they never look at the language the same way again.
Many things can be declared with a name and referenced later
in the TypeScript world, this includes variables and interfaces
as we can see below
tsTry
interfaceFruit {name : stringmass : numbercolor : string}constbanana :Fruit = {name : "banana",color : "yellow",mass : 183,}
Let’s coin a term here and call banana
and Fruit
both
identifiers1, in that they provide a named reference
to some information (be it a value, or a type)
Stacking multiple things on an identifier
It may seem a little silly, but what if we renamed
banana
to Fruit
. What do you think would happen?
tsTry
interfaceFruit {name : stringmass : numbercolor : string}constFruit = {name : "banana",color : "yellow",mass : 183,}export {Fruit }
This is probably surprising for some readers — especially
the tooltip on the export
.
in fact, there’s a third kind of thing we can stack on this
called a namespace
(we’ll talk more about this later)
tsTry
classFruit {staticcreateBanana ():Fruit {return {name : "banana",color : "yellow",mass : 183 }}}// the namespacenamespaceFruit {functioncreateFruit ():Fruit {// the typereturnFruit .createBanana () // the class}}interfaceFruit {name : stringmass : numbercolor : string}export {Fruit }
I propose that in the situation above, we have one identifier that’s three things in one:
- a value (class)
- a type
- a namespace
Proving this hypothesis will be easier if we have some way to tell what’s on an identifier
How to tell what’s on an identifier
Tooltips, and attempts to use identifiers in certain positions are a great mechanism of understanding what we’re dealing with
tsTry
constis_a_value = 4typeis_a_type = {}namespaceis_a_namespace {constfoo = 17}// how to test for a valueconstx =is_a_value // the value position (RHS of =).// how to test for a typeconsty :is_a_type = {} // the type position (LHS of = ).// how to test for a namespace (hover over is_a_namespace symbol)is_a_namespace
Let’s look at some failing cases to convince ourselves that these tests work
tsTry
constis_a_value = 4typeis_a_type = {}// how to test for a valueconst'is_a_type' only refers to a type, but is being used as a value here.2693'is_a_type' only refers to a type, but is being used as a value here.x =is_a_type // how to test for a typeconst'is_a_value' refers to a value, but is being used as a type here. Did you mean 'typeof is_a_value'?2749'is_a_value' refers to a value, but is being used as a type here. Did you mean 'typeof is_a_value'?y := {} is_a_value
The namespace
test is a bit self-explanatory, so we’ve left that out, but hopefully this is convincing enough
A short aside: what’s the point namespace
?
Do any of you remember using jQuery?
To describe the way this library works using type information, you need to be able to handle cases like
tsTry
$ .ajax ({url : "/api/getWeather",data : {zipcode : 97201,},success : function (result ) {$ ("#weather-temp")[0].innerHTML ="<strong>" +result + "</strong> degrees"},})$ ("h1.title").forEach ((node ) => {node .tagName // "h1"})
We could define a function and a namespace that “stack” like
this, so that $
could simultaneously be invoked directly, and
serve as a namespace for things like $.ajax
,
$.getJSON
and so on…
tsTry
function$ (selector : string):NodeListOf <Element > {returndocument .querySelectorAll (selector )}namespace$ {export functionajax (arg : {url : stringdata : anysuccess : (response : any) => void}):Promise <any> {returnPromise .resolve ()}}
Generally, writing code in this way is a bit outdated, left over from
the days where we’d refer to libraries through a single global variable.
With this in mind, let’s not give namespace
too much more thought for now.
A look back on class
With our new knowledge of “things that can stack on an identifier”, let’s
take another close look at a class
in TypeScript
tsTry
classFruit {name ?: stringmass ?: numbercolor ?: stringstaticcreateBanana ():Fruit {return {name : "banana",color : "yellow",mass : 183 }}}
and let’s apply our type
and value
tests to this Fruit
identifier
tsTry
// how to test for a valueconstvalueTest =Fruit // Fruit is a value!valueTest .createBanana // how to test for a typelettypeTest :Fruit = {} as any // Fruit is a type!typeTest .color
So it seems that classes are both a type and a value.
The word completions for the letter c
above are a clue as to what’s going on:
- When
Fruit
is used as a type, it describes the type of an instance of Fruit - When
Fruit
is used as a value, it can both act as the constructor (e.g.,new Fruit()
) and holds the “static side” of the class (createBanana()
in this case)
-
TypeScript internally calls this a
↩ts.Symbol
, not to be confused with the JavaScript concept of the same name.