Object Types Flashcards

1
Q

How can you name an object type?

A

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;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

How can you define an optional propery?

A

You can mark optional properties by adding a question mark (?) to the end of their names.

interface PaintOptions {
  shape: Shape;
  xPos?: number;
  yPos?: number;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

How can you mark a property as read only?

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

What are Index Signatures?

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Is it possible to define an Index Signature with keys as string and another Index Signature with keys as number?

A

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;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Is it possible to use both properties and Index Signatures to define an Object Type?

A

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
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

How can you create a readonly array with index signatures?

A
interface ReadonlyStringArray {
  readonly [index: number]: string;
}
 
let myArray: ReadonlyStringArray = getReadOnlyStringArray();
myArray[2] = "Mallory"; // Error Index signature in type 'ReadonlyStringArray' only permits reading.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

How can you “extend” an interface type?

A

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.

interfaces 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,
};
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

How can you create an intersection type?

A

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;
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

What are the differences between interfaces and intersection types?

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What is a generic object type?

A

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
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Is the Array a generic object type?

A

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>.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

What is the ReadonlyArray Type?

A

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 Arrays to ReadonlyArrays.

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[]'.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

What are tuple types?

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Is it possible to destructure tuple types?

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Can we have optional properties on a tuple type?

A

Yes, 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.

type Either2dOr3d = [number, number, number?];
 
function setCoordinate(coord: Either2dOr3d) {
  const [x, y, z] = coord; // typeof z === number | undefined
 
  console.log(`Provided coordinates had ${coord.length} dimensions`); // typeof (property) length: 2 | 3
}

“Tuple Types” (typescriptlang.org). Retrieved March 30, 2023.

17
Q

Can tuples have rest elements?

A

Yes, tuples can also have rest elements, which have to be an array/tuple type.

type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];

StringNumberBooleans describes a tuple whose first two elements are string and number respectively, but which may have any number of booleans following.
StringBooleansNumber describes a tuple whose first element is string and then any number of booleans and ending with a number.
BooleansStringNumber describes a tuple whose starting elements are any number of booleans 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.

“Tuple Types” (typescriptlang.org). Retrieved March 30, 2023.

18
Q

Why are optional and rest elements useful in tuple types?

A

It allows TypeScript to correspond tuples with parameter lists. Tuples types can be used in rest parameters and arguments, so that the following:

function readButtonInput(...args: [string, number, ...boolean[]]) {
  const [name, version, ...input] = args;
  // ...
}

is basically equivalent to:

function readButtonInput(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.

“Tuple Types” (typescriptlang.org). Retrieved March 30, 2023.