What sets apart Record<key, type> from [key: string]: type?

Can someone explain the distinction between Record<key, type> and [key: string]: type? Are they interchangeable? Which one offers more flexibility and type safety?

I have come across a situation where both were used interchangeably. Is there a preferred practice for using them?

//Example using Record
interface PageInfo {
  title: string;
}

type Page = "home" | "about" | "contact";

const nav: Record<Page, PageInfo> = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home" },
};

console.log(nav.about);
// Object example

type Page2 = {
  [key: string]: PageInfo;
};

const navHtml: Page2 = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home" },
};

console.log(navHtml.contact);

Check out this Typescript playground for a live demo of this example

Answer №1

Initially, to my knowledge, none of them are entirely type safe.

Refer to this problem

You have the option to use a union type as a key in Record - Record<Page, PageInfo>, which is not possible in an indexed type:

type Page2 = {
  [key: string | number | symbol]: PageInfo; // error
};

For example:

//Record example
interface PageInfo {
  title: string;
}

type Page = "home" | "about" | "contact";

const nav: Record<Page, PageInfo> = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home" },
};

console.log(nav.about);
// object example

type Page2 = {
  [key: string]: PageInfo;
};

const navHtml: Page2 = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home" },
};

console.log(navHtml.contact);

/**
 * If you have a string as a key - no problem
 */
type Test1 = Record<string, string> extends { [p: string]: string } ? true : false // true
type Test2 = { [p: string]: string } extends Record<string, string> ? true : false // true

const foo = (arg: Record<string, string>) => arg
const indexed: { [p: string]: string } = { bar: 'bar' }
foo(indexed) // no error

/**
 * And vice-versa
 */
const bar = (arg: { [p: string]: string }) => arg
const record: Record<string, string> = { bar: 'bar' }
foo(record) // no error

/**
 * But if you have a union type as a key
 * Above approach will not work
 */
type Test3 = Record<'a' | 'b', string> extends { [p: string]: string } ? true : false // true
type Test4 = { [p: string]: string } extends Record<'a' | 'b', string> ? true : false // false

const withIndex: Record<'a' | 'b', string> = { bar: 'bar' } // missing a,b

UPDATE

/**
 * Explanation of Record<'a'|'b', string>
 * It is mean that objects should have a & b keys
 */

type CustomRecord = Record<'a' | 'b', string>

const allowedRecord: CustomRecord = {
  a: '1',
  b: '2'
} // ok

const allowedRecord2: CustomRecord = {
  a: '1',
} // error, because without b

const allowedRecord3: CustomRecord = {
  b: '1',
} // error, because without a

/**
 * You are unable to do same thing with indexed type
 */
type TypeIndexedWIthExplicitKeys = {
  [p: string | number]: string
}

interface InterfaceIndexedWIthExplicitKeys {
  [p: 'a' | 'b']: string
}

const x: InterfaceIndexedWIthExplicitKeys = { 2: 's' } // no error, but.... I would expect an error
const y: TypeIndexedWIthExplicitKeys = { 2: 's' } // no error, but.... I would expect an error


const check = (): InterfaceIndexedWIthExplicitKeys => {
return { 2: 2 } // no error, but I would expect
}
type MyRecord = Record<'a' | 'b', string>

const z: MyRecord = { 2: '2' } // error, because we expect a & b
const c: MyRecord = { a: '2', b: '3' } // ok

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Are there advantages to incorporating d3.js through npm in an Angular 2 TypeScript project?

Summary: It is recommended to install d3.js via npm along with the typings. The accepted answer provides more details on this issue. This question was asked during my initial stages of learning Angular. The npm process is commonly used for tree-shaking, pa ...

Is there a way for me to indicate to my custom component the specific location within an input message where a value should be displayed

I'm currently developing a value selector component in ionic/angular and I've encountered an issue with the message/title that I want to pass to the component. I want to be able to specify where the selected value should appear within the message ...

TypeScript infers the return type by analyzing the callback property of an object parameter

I am facing a challenge while trying to determine the return type for the function post. The function's second parameter is an object that contains a transform property. If the transform parameter is provided, then the return type of post should be th ...

Encountering a problem while attempting to transfer data from a functional component to a class component in React with Typescript

In one of my pages, specifically the roleCategory.tsx (a functional component), I am utilizing the Navigate function to send a value named FromPage to another page called Home.tsx (which is a Class component). Below is the snippet of code where this functi ...

What is the correct method to obtain a reference to the host directive within a ControlValueAccessor implementation?

Is there a proper way to connect two directives, or a directive to a component (which is a directive as well) in angular2 following the "angular way of writing code"? Given the limited documentation on angular2, any insights or references on this topic wo ...

"Error: Import statement must be used within a module" encountered in TypeScript (with nodemon) and Node.js (running in Docker)

Within the server directory of my web application written in TypeScript, there is a nodemon command used to automatically restart the code after changes are made. The command looks like this: nodemon dist/index.js However, upon running it, an error is enc ...

What is the best way to retry an action stream observable in Angular/RxJS after it fails?

Kindly disregard the variable names and formatting alterations I've made. I've been attempting to incorporate RxJS error handling for an observable that triggers an action (user click) and then sends the request object from our form to execute a ...

Transferring data between unrelated components

I am facing an issue where I am unable to pass a value from the Tabs component to the Task component. To address this, I have created a separate data service. The value in the Tabs component is obtained as a parameter from another component. However, when ...

Encountered an issue when attempting to send data using this.http.post in Angular from the client's perspective

Attempting to transfer data to a MySQL database using Angular on the client-side and Express JS on the server-side. The post function on the server side works when tested with Postman. Here is the code snippet: app.use(bodyParser.json()); app.use(bodyPa ...

Implementing conditional properties in Typescript based on the value of another property

Is it possible to make a property required in a type based on the presence of another property? Here's an example: type Parent = { children?: Child[]; childrenIdSequence: string[]; // This property should only be required when `children` is prov ...

Adding a method to an object with TypeScript: A step-by-step guide

In my current scenario, I am faced with a challenge where I need to test a function with a specific use of this. However, typescript poses constraints by either disallowing me from adding the method to the object, or if I define it as any, then my interfac ...

Troubleshooting Next.js and Tailwind CSS Breakpoints: What's causing the

Having trouble with my custom breakpoints. For instance, I attempted the following: <div className="flex flex-row gap-5 mb-5 md:ml-15 sm:ml-15"> ... </div> The margin is not being applied on small and medium screens. Here are the th ...

The term 'App' is being referenced as a value when it is intended to be a type. Perhaps you meant 'typeof App'?

I am eager to master Typescript with React through hands-on experience, so I recently made the manual transition from JavaScript to TypeScript in my create-react-app. However, when working with my default testing file App.test.ts: import { render, screen ...

Tips for detecting when the enter key is pressed using Typescript

How can I detect when the enter key is pressed in a form element in Typescript by attaching a keypress event? Below is the code from my index.ts file: const search_field = document.querySelector('#search-field'); search_field?.addEventListener(& ...

What is the process for converting TSX files into JSX format?

Recently, I completed a project using Expo and TypeScript due to my familiarity with type-safe languages. However, now I need to convert the code to Expo written in JavaScript. While I could manually remove the types as I work through it, I am curious if ...

Angular 2: Sending an HTTP GET request with custom headers and parameters

I have been encountering difficulties while attempting to retrieve data from a Stardog triple store into Angular. Despite successfully accessing the service using curl with matching headers and parameters, I am unable to replicate this functionality with ...

The operator is being invoked multiple times beyond originally anticipated

I am currently working on developing code that paginates a result set using the expand operator until a specific number of resources have been fetched. Below is the code snippet I have written so far (excluding the actual async call logic): import { Obser ...

Tips for accessing a Literal type in TypeScript instead of a Union type

I recently developed a function to generate dynamic elements. However, I encountered an issue where instead of returning the precise type of the element, it was producing a union of various <HTMLElementTagNameMap> types. function createCustomElement( ...

What is the correct way to utilize a class variable within the function() method?

Class Variable Name: addPointY Utilizing "addPointY" in a Function: setInterval(function () { var y = Math.round(Math.random() * 100); series.addPoint(this.addPointY, true, true); }, 3000); I am tasked with finding a solution for this. It is a c ...

What are the steps for creating and deploying a project that utilizes asp.net core on the server-side and Angular on the client-side

My latest project combines asp.net core 5 and angular 15 technologies for the backend and frontend, respectively. The asp.net core MVC portion of the project is contained in a dedicated folder named serverApi, while the angular part is generated in another ...