Object Types Flashcards
How can you name an object type?
They can be named by using either an interface
interface Person { name: string; age: number; } function greet(person: Person) { return "Hello " + person.name; }
or a type
alias.
type Person = { name: string; age: number; }; function greet(person: Person) { return "Hello " + person.name; }
typescriptlang.org “Documentation - Object Types”. Retrieved 2023-03-25.
How can you define an optional propery?
You can mark optional properties by adding a question mark (?
) to the end of their names.
interface PaintOptions { shape: Shape; xPos?: number; yPos?: number; }
typescriptlang.org. “Documentation - Object Properties”. Retrieved 2023-03-25.
How can you mark a property as read only?
Prepending the property with readonly
.
interface SomeType { readonly prop: string; } function doSomething(obj: SomeType) { // We can read from 'obj.prop'. console.log(`prop has the value '${obj.prop}'.`); // But we can't re-assign it. obj.prop = "hello"; // Error: Cannot assign to 'prop' because it is a read-only property. }
Note: A property marked as readonly can’t be written to during type-checking. But that does not happen at runtime.
typescriptlang.org. “Documentation - readonly properties”. Retrieved 2023-03-25.
What are 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:
interface StringArray { [index: number]: string; } const myArray: StringArray = getStringArray(); const secondItem = myArray[1]; // typeof secondItem is string
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.
“Index Signatures” (typescriptlang.org). Retrieved 2023-03-27.
Is it possible to define an Index Signature with keys as string
and another Index Signature with keys as number
?
Yes, but with limitations.
It is possible to support both types of indexers, but 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.
interface Animal { name: string; } interface Dog extends Animal { breed: string; } // Error: indexing with a numeric string might get you a completely separate type of Animal! interface NotOkay { [x: number]: Animal; 'number' index type 'Animal' is not assignable to 'string' index type 'Dog'. [x: string]: Dog; }
“Index Signatures” (typescriptlang.org). Retrieved 2023-03-27.
Is it possible to use both properties and Index Signatures to define an Object Type?
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:
interface NumberDictionary { [index: string]: number; length: number; // ok name: string; Property 'name' of type 'string' is not assignable to 'string' index type 'number'. }
However, properties of different types are acceptable if the index signature is a union of the property types:
interface NumberOrStringDictionary { [index: string]: number | string; length: number; // ok, length is a number name: string; // ok, name is a string }
“Index Signatures” (typescriptlang.org). Retrieved 2023-03-27.
How can you create a readonly
array with index signatures?
interface ReadonlyStringArray { readonly [index: number]: string; } let myArray: ReadonlyStringArray = getReadOnlyStringArray(); myArray[2] = "Mallory"; // Error Index signature in type 'ReadonlyStringArray' only permits reading.
“Index Signatures” (typescriptlang.org). Retrieved 2023-03-27.
How can you “extend” an interface
type?
With the extends
keyword.
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.
interface
s can also extend from multiple types.
interface Colorful { color: string; } interface Circle { radius: number; } interface ColorfulCircle extends Colorful, Circle {} const cc: ColorfulCircle = { color: "red", radius: 42, };
“Extending Types” (typescriptlang.org). Retrieved 2023-03-27.
How can you create an intersection type?
Intersection types are types that combine existing object types.
An intersection type is defined using the &
operator.
interface Colorful { color: string; } interface Circle { radius: number; } type ColorfulCircle = Colorful & Circle;
“Intersection Types”(typescriptlang.org). Retrieved March 28, 2023.
What are the differences between interfaces and intersection types?
Two main differences:
- How members with the same property key are handled when present in both types.
Consider:
interface NumberToStringConverter { convert: (value: number) => string; } interface BidirectionalStringNumberConverter extends NumberToStringConverter { convert: (value: string) => number; }
The extends
above results in an error because the derriving interface declares a property with the same key as one in the derived interface but with an incompatible signature.
error TS2430: Interface 'BidirectionalStringNumberConverter' incorrectly extends interface 'NumberToStringConverter'. Types of property 'convert' are incompatible. Type '(value: string) => number' is not assignable to type '(value: number) => string'. Types of parameters 'value' and 'value' are incompatible. Type 'number' is not assignable to type 'string'.
However, if we employ intersection types
type NumberToStringConverter = { convert: (value: number) => string; } type BidirectionalStringNumberConverter = NumberToStringConverter & { convert: (value: string) => number; }
There is no error whatsoever and further given
// And this is a good thing indeed as a value conforming to the type is easily conceived const converter: BidirectionalStringNumberConverter = { convert: (value: string | number) => { return (typeof value === 'string' ? Number(value) : String(value)) as string & number; // type assertion is an unfortunately necessary hack. } } const s: string = converter.convert(0); // `convert`'s call signature comes from `NumberToStringConverter` const n: number = converter.convert('a'); // `convert`'s call signature comes from `BidirectionalStringNumberConverter`
-
Interface declarations are open ended. New members can be added anywhere because multiple interface declarations with same name in the same declaration space are merged. By contrast, intersection types, as
stored
in a type declaration, are closed, not subject to merging.
Here is a common use for merging behavior
interface Array<T> { // map, filter, etc. } interface Array<T> { flatMap<R>(f: (x: T) => R[]): R[]; } if (typeof Array.prototype.flatMap !== 'function') { Array.prototype.flatMap = function (f) { // Implementation simplified for exposition. return this.map(f).reduce((xs, ys) => [...xs, ...ys], []); } }
Notice how no extends
clause is present, although specified in separate interface
declarations. Both are merged by name into a single logical interface declaration that has both sets of members.
“Difference between extending and intersecting interfaces in TypeScript?” (Stack Overflow). Retrieved March 28, 2023.
What is a generic object type?
A generic type is can be seen as a template where the generic type is a placeholder that will be replaced by a real type whenever the generic type is assigned.
interface Box<Type> { contents: Type; } let boxA: Box<string> = { contents: "hello" }; boxA.contents; // Type of contents is string
“Generic Object Types” (typescriptlang.org). Retrieved March 28, 2023.
Is the Array
a generic object type?
Yes, the Array type is a generic object type. Whenever we write out types like number[]
or string[]
, that’s really just a shorthand for Array<number>
and Array<string>
.
“The Array Type” (typescriptlang.org). Retrieved March 29, 2023.
What is the ReadonlyArray
Type?
The ReadonlyArray
is a special type that describes arrays that shouldn’t be changed.
Much like the readonly
modifier for properties, it’s mainly a tool we can use for intent. When we see a function that returns ReadonlyArrays
, it tells us we’re not meant to change the contents at all, and when we see a function that consumes ReadonlyArrays
, 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. Instead, we can assign regular Array
s to ReadonlyArray
s.
const roArray: 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[]
.
let x: readonly string[] = []; let y: string[] = []; x = y; y = x; // Error The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.
“The ReadonlyArray Type” (typescriptlang.org). Retrieved March 29, 2023.
What are 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.
type StringNumberPair = [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
.
“Tuple Types” (typescriptlang.org). Retrieved March 30, 2023.
Is it possible to destructure tuple types?
Yes, it is possible to destructure tuples using JavaScript’s array destructuring.
function doSomething(stringHash: [string, number]) { const [inputString, hash] = stringHash; console.log(inputString); // typeof inputString === string console.log(hash); // typeof hash === number }
“Tuple Types” (typescriptlang.org). Retrieved March 30, 2023.