In JavaScript, the fundamental way that we group and pass around data is through objects. In TypeScript, we represent those through object types .
As we’ve seen, they can be anonymous:
tsTry
functiongreet (person : {name : string;age : number }) {return "Hello " +person .name ;}
or they can be named by using either an interface:
tsTry
interfacePerson {name : string;age : number;}functiongreet (person :Person ) {return "Hello " +person .name ;}
or a type alias:
tsTry
typePerson = {name : string;age : number;};functiongreet (person :Person ) {return "Hello " +person .name ;}
In all three examples above, we’ve written functions that take objects that contain the property
name
(which must be a
string
) and
age
(which must be a
number
).
Quick Reference
We have cheat-sheets available for both
type
and
interface
, if you want a quick look at the important every-day syntax at a glance.
Property Modifiers
Each property in an object type can specify a couple of things: the type, whether the property is optional, and whether the property can be written to.
Optional Properties
Much of the time, we’ll find ourselves dealing with objects that
might
have a property set.
In those cases, we can mark those properties as
optional
by adding a question mark (
?
) to the end of their names.
tsTry
interfacePaintOptions {shape :Shape ;xPos ?: number;yPos ?: number;}functionpaintShape (opts :PaintOptions ) {// ...}constshape =getShape ();paintShape ({shape });paintShape ({shape ,xPos : 100 });paintShape ({shape ,yPos : 100 });paintShape ({shape ,xPos : 100,yPos : 100 });
In this example, both
xPos
and
yPos
are considered optional.
We can choose to provide either of them, so every call above to
paintShape
is valid.
All optionality really says is that if the property
is
set, it better have a specific type.
We can also read from those properties - but when we do under
strictNullChecks
, TypeScript will tell us they’re potentially
undefined
.
ts
functionpaintShape (opts :PaintOptions ) {letxPos =opts .xPos ;
In JavaScript, even if the property has never been set, we can still access it - it’s just going to give us the value
undefined
.
We can just handle
undefined
specially by checking for it.
ts
functionpaintShape (opts :PaintOptions ) {letxPos =opts .xPos ===undefined ? 0 :opts .xPos ;
Note that this pattern of setting defaults for unspecified values is so common that JavaScript has syntax to support it.
ts
functionpaintShape ({shape ,xPos = 0,yPos = 0 }:PaintOptions ) {console .log ("x coordinate at",xPos );
Here we used
a destructuring pattern
for
paintShape
’s parameter, and provided
default values
for
xPos
and
yPos
.
Now
xPos
and
yPos
are both definitely present within the body of
paintShape
, but optional for any callers to
paintShape
.
Note that there is currently no way to place type annotations within destructuring patterns. This is because the following syntax already means something different in JavaScript.
tsTry
functiondraw ({shape :Shape ,xPos :number = 100 /*...*/ }) {Cannot find name 'shape'. Did you mean 'Shape'?2552Cannot find name 'shape'. Did you mean 'Shape'?render (); shape Cannot find name 'xPos'.2304Cannot find name 'xPos'.render (); xPos }
In an object destructuring pattern,
shape: Shape
means “grab the property
shape
and redefine it locally as a variable named
Shape
.”
Likewise
xPos: number
creates a variable named
number
whose value is based on the parameter’s
xPos
.
readonly
Properties
Properties can also be marked as
readonly
for TypeScript.
While it won’t change any behavior at runtime, a property marked as
readonly
can’t be written to during type-checking.
tsTry
interfaceSomeType {readonlyprop : string;}functiondoSomething (obj :SomeType ) {// We can read from 'obj.prop'.console .log (`prop has the value '${obj .prop }'.`);// But we can't re-assign it.Cannot assign to 'prop' because it is a read-only property.2540Cannot assign to 'prop' because it is a read-only property.obj .= "hello"; prop }
Using the
readonly
modifier doesn’t necessarily imply that a value is totally immutable - or in other words, that its internal contents can’t be changed.
It just means the property itself can’t be re-written to.
tsTry
interfaceHome {readonlyresident : {name : string;age : number };}functionvisitForBirthday (home :Home ) {// We can read and update properties from 'home.resident'.console .log (`Happy birthday ${home .resident .name }!`);home .resident .age ++;}functionevict (home :Home ) {// But we can't write to the 'resident' property itself on a 'Home'.Cannot assign to 'resident' because it is a read-only property.2540Cannot assign to 'resident' because it is a read-only property.home .= { resident name : "Victor the Evictor",age : 42,};}
It’s important to manage expectations of what
readonly
implies.
It’s useful to signal intent during development time for TypeScript on how an object should be used.
TypeScript doesn’t factor in whether properties on two types are
readonly
when checking whether those types are compatible, so
readonly
properties can also change via aliasing.
tsTry
interfacePerson {name : string;age : number;}interfaceReadonlyPerson {readonlyname : string;readonlyage : number;}letwritablePerson :Person = {name : "Person McPersonface",age : 42,};// worksletreadonlyPerson :ReadonlyPerson =writablePerson ;console .log (readonlyPerson .age ); // prints '42'writablePerson .age ++;console .log (readonlyPerson .age ); // prints '43'
Using
mapping modifiers
, you can remove
readonly
attributes.
Index Signatures
Sometimes you don’t know all the names of a type’s properties ahead of time, but you do know the shape of the values.
In those cases you can use an index signature to describe the types of possible values, for example:
ts
interfaceStringArray {[index : number]: string;}constmyArray :StringArray =getStringArray ();constsecondItem =myArray [1];
Above, we have a
StringArray
interface which has an index signature.
This index signature states that when a
StringArray
is indexed with a
number
, it will return a
string
.
Only some types are allowed for index signature properties:
string
,
number
,
symbol
, template string patterns, and union types consisting only of these.
It is possible to support multiple types of indexers. Note that when using both `number` and `string` indexers, the type returned from a numeric indexer must be a subtype of the type returned from the string indexer. This is because when indexing with a
number
, JavaScript will actually convert that to a
string
before indexing into an object. That means that indexing with
100
(a
number
) is the same thing as indexing with
"100"
(a
string
), so the two need to be consistent.
tsTry
interfaceAnimal {name : string;}interfaceDog extendsAnimal {breed : string;}// Error: indexing with a numeric string might get you a completely separate type of Animal!interfaceNotOkay {['number' index type 'Animal' is not assignable to 'string' index type 'Dog'.2413'number' index type 'Animal' is not assignable to 'string' index type 'Dog'.x : number]:Animal ;[x : string]:Dog ;}
While string index signatures are a powerful way to describe the “dictionary” pattern, they also enforce that all properties match their return type.
This is because a string index declares that
obj.property
is also available as
obj["property"]
.
In the following example,
name
’s type does not match the string index’s type, and the type checker gives an error:
tsTry
interfaceNumberDictionary {[index : string]: number;length : number; // okProperty 'name' of type 'string' is not assignable to 'string' index type 'number'.2411Property 'name' of type 'string' is not assignable to 'string' index type 'number'.: string; name }
However, properties of different types are acceptable if the index signature is a union of the property types:
tsTry
interfaceNumberOrStringDictionary {[index : string]: number | string;length : number; // ok, length is a numbername : string; // ok, name is a string}
Finally, you can make index signatures
readonly
in order to prevent assignment to their indices:
tsTry
interfaceReadonlyStringArray {readonly [index : number]: string;}letmyArray :ReadonlyStringArray =getReadOnlyStringArray ();Index signature in type 'ReadonlyStringArray' only permits reading.2542Index signature in type 'ReadonlyStringArray' only permits reading.myArray [2] = "Mallory";
You can’t set
myArray[2]
because the index signature is
readonly
.
Excess Property Checks
Where and how an object is assigned a type can make a difference in the type system. One of the key examples of this is in excess property checking, which validates the object more thoroughly when it is created and assigned to an object type during creation.
tsTry
interfaceSquareConfig {color ?: string;width ?: number;}functioncreateSquare (config :SquareConfig ): {color : string;area : number } {return {color :config .color || "red",area :config .width ?config .width *config .width : 20,};}letObject literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?2561Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?mySquare =createSquare ({: "red", colour width : 100 });
Notice the given argument to
createSquare
is spelled
colour
instead of
color
.
In plain JavaScript, this sort of thing fails silently.
You could argue that this program is correctly typed, since the
width
properties are compatible, there’s no
color
property present, and the extra
colour
property is insignificant.
However, TypeScript takes the stance that there’s probably a bug in this code. Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments. If an object literal has any properties that the “target type” doesn’t have, you’ll get an error:
tsTry
letObject literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?2561Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'. Did you mean to write 'color'?mySquare =createSquare ({: "red", colour width : 100 });
Getting around these checks is actually really simple. The easiest method is to just use a type assertion:
tsTry
letmySquare =createSquare ({width : 100,opacity : 0.5 } asSquareConfig );
However, a better approach might be to add a string index signature if you’re sure that the object can have some extra properties that are used in some special way.
If
SquareConfig
can have
color
and
width
properties with the above types, but could
also
have any number of other properties, then we could define it like so:
tsTry
interfaceSquareConfig {color ?: string;width ?: number;[propName : string]: unknown;}
Here we’re saying that
SquareConfig
can have any number of properties, and as long as they aren’t
color
or
width
, their types don’t matter.
One final way to get around these checks, which might be a bit surprising, is to assign the object to another variable:
Since assigning
squareOptions
won’t undergo excess property checks, the compiler won’t give you an error:
tsTry
letsquareOptions = {colour : "red",width : 100 };letmySquare =createSquare (squareOptions );
The above workaround will work as long as you have a common property between
squareOptions
and
SquareConfig
.
In this example, it was the property
width
. It will however, fail if the variable does not have any common object property. For example:
tsTry
letsquareOptions = {colour : "red" };letType '{ colour: string; }' has no properties in common with type 'SquareConfig'.2559Type '{ colour: string; }' has no properties in common with type 'SquareConfig'.mySquare =createSquare (); squareOptions
Keep in mind that for simple code like above, you probably shouldn’t be trying to “get around” these checks. For more complex object literals that have methods and hold state, you might need to keep these techniques in mind, but a majority of excess property errors are actually bugs.
That means if you’re running into excess property checking problems for something like option bags, you might need to revise some of your type declarations.
In this instance, if it’s okay to pass an object with both a
color
or
colour
property to
createSquare
, you should fix up the definition of
SquareConfig
to reflect that.
Extending Types
It’s pretty common to have types that might be more specific versions of other types.
For example, we might have a
BasicAddress
type that describes the fields necessary for sending letters and packages in the U.S.
tsTry
interfaceBasicAddress {name ?: string;street : string;city : string;country : string;postalCode : string;}
In some situations that’s enough, but addresses often have a unit number associated with them if the building at an address has multiple units.
We can then describe an
AddressWithUnit
.
tsTry
interfaceAddressWithUnit {name ?: string;unit : string;street : string;city : string;country : string;postalCode : string;}
This does the job, but the downside here is that we had to repeat all the other fields from
BasicAddress
when our changes were purely additive.
Instead, we can extend the original
BasicAddress
type and just add the new fields that are unique to
AddressWithUnit
.
tsTry
interfaceBasicAddress {name ?: string;street : string;city : string;country : string;postalCode : string;}interfaceAddressWithUnit extendsBasicAddress {unit : string;}
The
extends
keyword on an
interface
allows us to effectively copy members from other named types, and add whatever new members we want.
This can be useful for cutting down the amount of type declaration boilerplate we have to write, and for signaling intent that several different declarations of the same property might be related.
For example,
AddressWithUnit
didn’t need to repeat the
street
property, and because
street
originates from
BasicAddress
, a reader will know that those two types are related in some way.
interface
s can also extend from multiple types.
tsTry
interfaceColorful {color : string;}interfaceCircle {radius : number;}interfaceColorfulCircle extendsColorful ,Circle {}constcc :ColorfulCircle = {color : "red",radius : 42,};
Intersection Types
interface
s allowed us to build up new types from other types by extending them.
TypeScript provides another construct called
intersection types
that is mainly used to combine existing object types.
An intersection type is defined using the
&
operator.
tsTry
interfaceColorful {color : string;}interfaceCircle {radius : number;}typeColorfulCircle =Colorful &Circle ;
Here, we’ve intersected
Colorful
and
Circle
to produce a new type that has all the members of
Colorful
and
Circle
.
tsTry
functiondraw (circle :Colorful &Circle ) {console .log (`Color was ${circle .color }`);console .log (`Radius was ${circle .radius }`);}// okaydraw ({color : "blue",radius : 42 });// oopsObject literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?2561Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?draw ({color : "red",: 42 }); raidus
Interface Extension vs. Intersection
We just looked at two ways to combine types which are similar, but are actually subtly different.
With interfaces, we could use an
extends
clause to extend from other types, and we were able to do something similar with intersections and name the result with a type alias.
The principal difference between the two is how conflicts are handled, and that difference is typically one of the main reasons why you’d pick one over the other between an interface and a type alias of an intersection type.
If interfaces are defined with the same name, TypeScript will attempt to merge them if the properties are compatible. If the properties are not compatible (i.e., they have the same property name but different types), TypeScript will raise an error.
In the case of intersection types, properties with different types will be merged automatically. When the type is used later, TypeScript will expect the property to satisfy both types simultaneously, which may produce unexpected results.
For example, the following code will throw an error because the properties are incompatible:
ts
interface Person {name: string;}
interface Person {name: number;}
In contrast, the following code will compile, but it results in a
never
type:
ts
interfacePerson1 {name : string;}interfacePerson2 {name : number;}typeStaff =Person1 &Person2 declare conststaffer :Staff ;staffer .name ;
In this case, Staff would require the name property to be both a string and a number, which results in property being of type
never
.
Generic Object Types
Let’s imagine a
Box
type that can contain any value -
string
s,
number
s,
Giraffe
s, whatever.
tsTry
interfaceBox {contents : any;}
Right now, the
contents
property is typed as
any
, which works, but can lead to accidents down the line.
We could instead use
unknown
, but that would mean that in cases where we already know the type of
contents
, we’d need to do precautionary checks, or use error-prone type assertions.
tsTry
interfaceBox {contents : unknown;}letx :Box = {contents : "hello world",};// we could check 'x.contents'if (typeofx .contents === "string") {console .log (x .contents .toLowerCase ());}// or we could use a type assertionconsole .log ((x .contents as string).toLowerCase ());
One type safe approach would be to instead scaffold out different
Box
types for every type of
contents
.
tsTry
interfaceNumberBox {contents : number;}interfaceStringBox {contents : string;}interfaceBooleanBox {contents : boolean;}
But that means we’ll have to create different functions, or overloads of functions, to operate on these types.
tsTry
functionsetContents (box :StringBox ,newContents : string): void;functionsetContents (box :NumberBox ,newContents : number): void;functionsetContents (box :BooleanBox ,newContents : boolean): void;functionsetContents (box : {contents : any },newContents : any) {box .contents =newContents ;}
That’s a lot of boilerplate. Moreover, we might later need to introduce new types and overloads. This is frustrating, since our box types and overloads are all effectively the same.
Instead, we can make a
generic
Box
type which declares a
type parameter
.
tsTry
interfaceBox <Type > {contents :Type ;}
You might read this as “A
Box
of
Type
is something whose
contents
have type
Type
”.
Later on, when we refer to
Box
, we have to give a
type argument
in place of
Type
.
tsTry
letbox :Box <string>;
Think of
Box
as a template for a real type, where
Type
is a placeholder that will get replaced with some other type.
When TypeScript sees
Box<string>
, it will replace every instance of
Type
in
Box<Type>
with
string
, and end up working with something like
{ contents: string }
.
In other words,
Box<string>
and our earlier
StringBox
work identically.
ts
interfaceBox <Type > {contents :Type ;}interfaceStringBox {contents : string;}letboxA :Box <string> = {contents : "hello" };boxA .contents ;
Box
is reusable in that
Type
can be substituted with anything. That means that when we need a box for a new type, we don’t need to declare a new
Box
type at all (though we certainly could if we wanted to).
tsTry
interfaceBox <Type > {contents :Type ;}interfaceApple {// ....}// Same as '{ contents: Apple }'.typeAppleBox =Box <Apple >;
This also means that we can avoid overloads entirely by instead using generic functions .
tsTry
functionsetContents <Type >(box :Box <Type >,newContents :Type ) {box .contents =newContents ;}
It is worth noting that type aliases can also be generic. We could have defined our new
Box<Type>
interface, which was:
tsTry
interfaceBox <Type > {contents :Type ;}
by using a type alias instead:
tsTry
typeBox <Type > = {contents :Type ;};
Since type aliases, unlike interfaces, can describe more than just object types, we can also use them to write other kinds of generic helper types.
ts
typeOrNull <Type > =Type | null;typeOneOrMany <Type > =Type |Type [];typeOneOrManyOrNull <Type > =OrNull <OneOrMany <Type >>;
We’ll circle back to type aliases in just a little bit.
The
Array
Type
Generic object types are often some sort of container type that work independently of the type of elements they contain. It’s ideal for data structures to work this way so that they’re re-usable across different data types.
It turns out we’ve been working with a type just like that throughout this handbook: the
Array
type.
Whenever we write out types like
number[]
or
string[]
, that’s really just a shorthand for
Array<number>
and
Array<string>
.
tsTry
functiondoSomething (value :Array <string>) {// ...}letmyArray : string[] = ["hello", "world"];// either of these work!doSomething (myArray );doSomething (newArray ("hello", "world"));
Much like the
Box
type above,
Array
itself is a generic type.
tsTry
interfaceArray <Type > {/*** Gets or sets the length of the array.*/length : number;/*** Removes the last element from an array and returns it.*/pop ():Type | undefined;/*** Appends new elements to an array, and returns the new length of the array.*/push (...items :Type []): number;// ...}
Modern JavaScript also provides other data structures which are generic, like
Map<K, V>
,
Set<T>
, and
Promise<T>
.
All this really means is that because of how
Map
,
Set
, and
Promise
behave, they can work with any sets of types.
The
ReadonlyArray
Type
The
ReadonlyArray
is a special type that describes arrays that shouldn’t be changed.
tsTry
functiondoStuff (values :ReadonlyArray <string>) {// We can read from 'values'...constcopy =values .slice ();console .log (`The first value is ${values [0]}`);// ...but we can't mutate 'values'.Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.values .("hello!"); push }
Much like the
readonly
modifier for properties, it’s mainly a tool we can use for intent.
When we see a function that returns
ReadonlyArray
s, it tells us we’re not meant to change the contents at all, and when we see a function that consumes
ReadonlyArray
s, it tells us that we can pass any array into that function without worrying that it will change its contents.
Unlike
Array
, there isn’t a
ReadonlyArray
constructor that we can use.
tsTry
new'ReadonlyArray' only refers to a type, but is being used as a value here.2693'ReadonlyArray' only refers to a type, but is being used as a value here.("red", "green", "blue"); ReadonlyArray
Instead, we can assign regular
Array
s to
ReadonlyArray
s.
tsTry
constroArray :ReadonlyArray <string> = ["red", "green", "blue"];
Just as TypeScript provides a shorthand syntax for
Array<Type>
with
Type[]
, it also provides a shorthand syntax for
ReadonlyArray<Type>
with
readonly Type[]
.
tsTry
functiondoStuff (values : readonly string[]) {// We can read from 'values'...constcopy =values .slice ();console .log (`The first value is ${values [0]}`);// ...but we can't mutate 'values'.Property 'push' does not exist on type 'readonly string[]'.2339Property 'push' does not exist on type 'readonly string[]'.values .("hello!"); push }
One last thing to note is that unlike the
readonly
property modifier, assignability isn’t bidirectional between regular
Array
s and
ReadonlyArray
s.
tsTry
letx : readonly string[] = [];lety : string[] = [];x =y ;The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.4104The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.= y x ;
Tuple Types
A
tuple type
is another sort of
Array
type that knows exactly how many elements it contains, and exactly which types it contains at specific positions.
tsTry
typeStringNumberPair = [string, number];
Here,
StringNumberPair
is a tuple type of
string
and
number
.
Like
ReadonlyArray
, it has no representation at runtime, but is significant to TypeScript.
To the type system,
StringNumberPair
describes arrays whose
0
index contains a
string
and whose
1
index contains a
number
.
ts
functiondoSomething (pair : [string, number]) {consta =pair [0];
If we try to index past the number of elements, we’ll get an error.
tsTry
functiondoSomething (pair : [string, number]) {// ...constTuple type '[string, number]' of length '2' has no element at index '2'.2493Tuple type '[string, number]' of length '2' has no element at index '2'.c =pair [2 ];}
We can also destructure tuples using JavaScript’s array destructuring.
ts
functiondoSomething (stringHash : [string, number]) {const [inputString ,hash ] =stringHash ;console .log (inputString );
Tuple types are useful in heavily convention-based APIs, where each element’s meaning is “obvious”.
This gives us flexibility in whatever we want to name our variables when we destructure them.
In the above example, we were able to name elements
0
and
1
to whatever we wanted.
However, since not every user holds the same view of what’s obvious, it may be worth reconsidering whether using objects with descriptive property names may be better for your API.
Other than those length checks, simple tuple types like these are equivalent to types which are versions of
Array
s that declare properties for specific indexes, and that declare
length
with a numeric literal type.
tsTry
interfaceStringNumberPair {// specialized propertieslength : 2;0: string;1: number;// Other 'Array<string | number>' members...slice (start ?: number,end ?: number):Array <string | number>;}
Another thing you may be interested in is that tuples can have optional properties by writing out a question mark (
?
after an element’s type).
Optional tuple elements can only come at the end, and also affect the type of
length
.
ts
typeEither2dOr3d = [number, number, number?];functionsetCoordinate (coord :Either2dOr3d ) {const [x ,y ,z ] =coord ;
Tuples can also have rest elements, which have to be an array/tuple type.
tsTry
typeStringNumberBooleans = [string, number, ...boolean[]];typeStringBooleansNumber = [string, ...boolean[], number];typeBooleansStringNumber = [...boolean[], string, number];
StringNumberBooleans
describes a tuple whose first two elements are
string
and
number
respectively, but which may have any number of
boolean
s following.
StringBooleansNumber
describes a tuple whose first element is
string
and then any number of
boolean
s and ending with a
number
.
BooleansStringNumber
describes a tuple whose starting elements are any number of
boolean
s and ending with a
string
then a
number
.
A tuple with a rest element has no set “length” - it only has a set of well-known elements in different positions.
tsTry
consta :StringNumberBooleans = ["hello", 1];constb :StringNumberBooleans = ["beautiful", 2, true];constc :StringNumberBooleans = ["world", 3, true, false, true, false, true];
Why might optional and rest elements be useful? Well, it allows TypeScript to correspond tuples with parameter lists. Tuples types can be used in rest parameters and arguments , so that the following:
tsTry
functionreadButtonInput (...args : [string, number, ...boolean[]]) {const [name ,version , ...input ] =args ;// ...}
is basically equivalent to:
tsTry
functionreadButtonInput (name : string,version : number, ...input : boolean[]) {// ...}
This is handy when you want to take a variable number of arguments with a rest parameter, and you need a minimum number of elements, but you don’t want to introduce intermediate variables.
readonly
Tuple Types
One final note about tuple types - tuple types have
readonly
variants, and can be specified by sticking a
readonly
modifier in front of them - just like with array shorthand syntax.
tsTry
functiondoSomething (pair : readonly [string, number]) {// ...}
As you might expect, writing to any property of a
readonly
tuple isn’t allowed in TypeScript.
tsTry
functiondoSomething (pair : readonly [string, number]) {Cannot assign to '0' because it is a read-only property.2540Cannot assign to '0' because it is a read-only property.pair [0 ] = "hello!";}
Tuples tend to be created and left un-modified in most code, so annotating types as
readonly
tuples when possible is a good default.
This is also important given that array literals with
const
assertions will be inferred with
readonly
tuple types.
ts
letpoint = [3, 4] asconst ;functiondistanceFromOrigin ([x ,y ]: [number, number]) {returnMath .sqrt (x ** 2 +y ** 2);}Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'. The type 'readonly [3, 4]' is 'readonly' and cannot be assigned to the mutable type '[number, number]'.2345Argument of type 'readonly [3, 4]' is not assignable to parameter of type '[number, number]'.distanceFromOrigin (); point