Advanced TypeScript: Typeof
Welcome to the Typeof lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
We've applied static types to variables, to functions, to function parameters, etc. However, so far we haven't seen a way to go in the other direction. What if we want to get the type of a variable or function that we already declared?
TypeScript gives us the
typeofoperator for exactly that purpose.typeof someVariablegives us the type of that variable.>
const one: number = 1;const two: typeof one = 2;two;Result:
2
>
const one: number = 1;type OurType = typeof one;const two: OurType = 2;two;Result:
2
>
const one: number = 1;type OurType = typeof one;const aString: OurType = 'hello';aString;Result:
type error: Type 'string' is not assignable to type 'number'.
This
typeofoperator may remind you of the JavaScript'stypeof, which we also use in TypeScript. For example, we use it in type guards likeif (typeof s === 'string'). However, this newtypeofis a completely separate operator despite having the same name. We'll come back to this issue later in the lesson. For now, try to put what you know about the JavaScripttypeofon hold!The TypeScript
typeofoperator works with any type, no matter how complex. Here's an example with an array:>
const numbers = [1, 2];type OurVariableType = typeof numbers;const moreNumbers: OurVariableType = [3, 4, 5];moreNumbers;Result:
[3, 4, 5]
In JavaScript and TypeScript, functions are values. We can use
typeofto get their types, just like we did for numbers and arrays above.>
function double(n: number) {return n * 2;}// This gives us the type `(n: number) => number`.type OurFunctionType = typeof double;const quadruple: OurFunctionType = n => n * 4;quadruple(5);Result:
20
That code type checked because
doubleandquadruplehave the same type,(n: number) => number. But note that we never wrote that type out explicitly! We didn't even have to specify the type ofquadruple's argument,n, becauseOurFunctionTypetells TypeScript that it must be anumber.Types from
typeofare enforced like any other type. For example,OurFunctionTypewants onenumberparameter. If we try to use that type for a function that takes two parameters, that's a type error.>
function double(n: number) {return n * 2;}type OurFunctionType = typeof double;const add: OurFunctionType = (x: number, y: number) => x + y;add(1, 2);Result:
type error: Type '(x: number, y: number) => number' is not assignable to type '(n: number) => number'. Target signature provides too few arguments. Expected 2 or more, but got 1.
An earlier lesson showed the
ReturnTypeandParametersgeneric types, using them on function types that we defined explicitly. But we had no way to define a function and then useReturnTypeorParameterson it. Now we can usetypeofto do that! First we get the function's type withtypeof, then we extract its return type or parameter types.>
function double(n: number) {return n * 2;}type OurReturnType = ReturnType<typeof double>;const n: OurReturnType = 55;n;Result:
55
We can use
typeoftogether withReturnTypeandParametersto "disassemble" existing functions into their component types. Then we can build new functions out of those types.>
function double(n: number) {return n * 2;}type OurReturnType = ReturnType<typeof double>;const add = (x: number, y: number): OurReturnType => x + y;add(2, 3);Result:
5
>
function double(n: number) {return n * 2;}type OurReturnType = ReturnType<typeof double>;const add = (x: number, y: number): OurReturnType => (x + y).toString();add(2, 3);Result:
type error: Type 'string' is not assignable to type 'number'.
Sometimes
typeofgives surprising results. For example, earlier in this lesson we definedconst one: number = 1. As we might expect,typeof onewasnumber. But what if we defineconst one = 1, letting TypeScript infer the type? In that case, TypeScript infers the literal type1, sotypeof oneis1, notnumber!>
const one = 1;const two: typeof one = 2;two;Result:
type error: Type '2' is not assignable to type '1'.
Fortunately there's an easy workaround when this happens: add an explicit type annotation.
>
const one: number = 1;const two: typeof one = 2;two;Result:
2
Here's a code problem:
In the code below, we define a
doublefunction and aquadruplefunction. Currently, thequadruplefunction's type definition is missing.Add a type definition, but not by writing it out! Use
typeofto givequadruplethe same type asdouble. (You won't need to change any other part of the function.)function double(n: number): number {return n * 2;}const quadruple: typeof double= (n) => n * 4;; /* This extra semicolon prevents a potential problem when your answer doesn't* end in a semicolon. */[quadruple(3), quadruple(7)];- Goal:
[12, 28]
- Yours:
[12, 28]
The
typeofoperator illustrates an important concept in TypeScript, and in statically typed languages generally. There are two worlds in TypeScript: the value world and the type world. Values work like they do in JavaScript. If1 + 1 === 2in JavaScript, then1 + 1 === 2in TypeScript, and likewise for any other expression we can imagine.The type world is specific to TypeScript, and is strictly separated from the world of values. That's why we can't say
type SomeType = someVariable, and we can't sayconst someVariable = SomeType. These statements don't make sense because value expressions and type expressions are two separate sub-languages within TypeScript.When we write
const n: number, we're integrating types and values. That code says "at runtime we'll have a valuen, and we expect its runtime value to match the compile-time typenumber". Until this lesson, we only went in that direction: we had types, and we usedconst,function, etc. to declare values with those types.As of this lesson, we can now go in the other direction too. We have a variable holding a value like a number or a function, and we use
typeofto move from the value world to the type world. When we dotypeof someFunction, we're asking TypeScript what static typesomeFunctionalready has.As we mentioned in the beginning of the lesson, there are two separate operators named
typeof. The fact that they have the same name can be confusing at first. Crucially, onetypeofoperator only works in the value world, and the other in the type world.We've already seen many similar situations; TypeScript is full of them! For example, what does
{name: string}mean? Is it an object with anameproperty whose value comes from a variable namedstring?>
const string = 'Amir';const amir = {name: string};amir;Result:
{name: 'Amir'}Or is
{name: string}a TypeScript type where thenameproperty has the typestring?>
type User = {name: string};const amir: User = {name: 'Amir'};amir;Result:
{name: 'Amir'}The expression is identical in both cases:
{name: string}. In a value context, that's a literal object value that references a variable. In a type context, it's a type describing the shape of objects.Like the
{name: string}object syntax, the twotypeofoperators do different things despite having the same name. The new TypeScripttypeofoperator introduced in this lesson only works inside of TypeScript types, letting us access the type of an existing identifier like a variable. The old JavaScript operator returns a string, liketypeof 1 === 'number'. TypeScript extends that JavaScript operator to also do type narrowing, likeif (typeof x === 'number'), but even in TypeScript it only works on values.>
function doubleIfNumber(n: number | string): number | string {if (typeof n === 'number') {return n * 2;} else {return n;}}[doubleIfNumber(5),doubleIfNumber('a'),];Result:
[10, 'a']
This is another example of how TypeScript is actually two languages: it's JavaScript, plus a separate type language layered on top. With some practice, you'll use both
typeofoperators without having to think about it.