Exploring TypeScript <T that belongs to any array>

getLength function appears to be functional

Upon inspection, these two functions seem quite similar (The second one may be more versatile as it can handle objects with properties other than just arrays):

During runtime, both functions essentially translate to the same javascript code.

function getLength<T>(
  // v is an array of some type T
  // This could be the type 'any',
  // so I know nothing about what's inside
  v: T[] 
): number {
  return v.length;
}
function getLength2<T extends any[]>(
  // v is an array, I know nothing 
  // about what's inside
  v: T
): number {
  return v.length;
}

flattenArray... well, it's not working as expected

If I apply the same logic here, I begin encountering type errors.

function flattenArray<T>(
  a:T[][]
) : T[] {
  return a.reduce((prev:T[], curr:T[]) => [...prev,...curr], [] as T[]);
}
function flattenArray<T extends any[]>(
  a:T[]
) : T {
  return a.reduce((prev:T, curr:T) => [...prev,...curr], [] as T);
}

Errors:

  1. Type 'T[number][]' is not assignable to type 'T'
  2. Conversion of type 'never[]' to type 'T' may be a mistake

From my perspective, I can cast the flattened array result to the desired type and bypass the type system by creating an empty array.

function flattenArray<T extends any[]>(
  a:T[]
) : T {
  return a.reduce((prev:T, curr:T) => [...prev,...curr] as T, [] as unknown as T);
}

This raises concerns for me. I believe there may be aspects of the type system that I am not fully grasping. When I specify T extends any[], I intend for T to possess at least all the properties of an Array. It could have more, but not less. Therefore, I should be able to treat T as an Array.

Any insights on this matter would be greatly appreciated!

Answer №1

When you specify T extends any[], it means that T must be compatible with an array type but can have additional properties due to structural typing. While this is fine for the input of a function, it cannot be guaranteed for the output. For example:

function flattenArray<T extends any[]>(arr: T[]): T {
  return arr.reduce<T>((prev, curr) => [...prev, ...curr], []);
  // ------------------------------> ~~~~~~~~~~~~~~~~~~
  // Type 'T[number][]' is not assignable to type 'T'.
  // 'T[number][]' can be assigned to 'T', 
  // but 'T' could be a different subtype of 'any[]'
}

In the above code, the issue arises because the compiler cannot ensure that [...prev, ...curr] of type T[number][] will also be of type T</code. Especially when <code>T has extra properties, these properties will not be included in the new array. This can lead to unexpected behavior, such as in this case:

const array1 = Object.assign([1, 2, 3], { a: 1 });
const array2 = Object.assign([4, 5, 6], { a: 2 });
const flattenedArray = flattenArray([array1, array2]);
/* const flattenedArray: number[] & {
    a: number;
} */
try {
  console.log(flattenedArray.a.toFixed(2)); // Results in a runtime error
} catch (e) {
  console.log(e); // RUNTIME ERROR 💥! flattenedArray.a is undefined
}

In this case, the function flattenArray incorrectly claims to return a value of type T, which in this situation is number[] & {a: number}. This mismatch between the expected and actual types can result in runtime errors.

The resolution is to modify the function to return a value of type

T[number][]</code instead:</p>
<pre><code>function flattenArrayFixed<T extends any[]>(arr: T[]) {
  return arr.reduce<T[number][]>((prev, curr) => [...prev, ...curr], []);
}

const flattenedFixed = flattenArrayFixed([array1, array2]);
// const flattenedFixed: number[]

Now, flattenedFixed is correctly recognized as number[] without the extra property a. Attempting to access flattenedFixed.a will result in a compiler error, preventing such mistakes.

Link to code playground

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

"Utilizing Typescript's keyof operator on its own

I'm grappling with the challenge of creating a type that can utilize the typeof its own keys, but I'm hitting a roadblock. Below is a simplified version of what I'm attempting to achieve: type SpecialType = Record<string, (getter: <K e ...

Converting types to "any" and encountering the error message "There are two distinct types with the same name, but they are not related."

I am encountering some challenges while trying to use an NPM module that I developed along with its Typescript typings in another application. To simplify the examples, I will omit properties that are not relevant to the issue at hand. Within my module&ap ...

Faulty deduction can occur when implementing the return statement in an identity function, or when incorporating an optional parameter

Encountering strange behavior while working on identity functions in a wizard system schema. Using a constrained identity function for inference is causing issues with one property that cannot be inferred when using the following: When the return value fr ...

What are the steps to retrieve the original source code of an HTML file, specifically in Angular 2 RC4

Is there a way to retrieve the source code that I manually typed in my IDE using JavaScript? Note: I am working with angular2 rc4. I attempted to access it using Reflect.getMetadata, but encountered errors indicating that it is not functioning properly. ...

Verify if a given string exists within a defined ENUM in typescript

I have an enum called "Languages" with different language codes such as nl, fr, en, and de. export enum Languages { nl = 1, fr = 2, en = 3, de = 4 } Additionally, I have a constant variable named "language" assigned the value 'de'. My g ...

Accessing environment-based constants in TypeScript beyond the scope of Cypress.env()Is there a way to gain access to environment-specific constants

Imagine I have an API test and the URL and Credentials are different between production and development environments: before("Authenticate with auth token", async () => { await spec().post(`${baseUrl}/auth`) .withBody( { ...

Utilize puppeteer and web-vitals in NextJS to retrieve the web performance metrics of a website

I'm currently working on a basic tool in NextJS that uses puppeteer to fetch web vitals data from a given URL. However, I'm facing an issue where the results are not being printed out. What could be causing this problem? const browser = await pup ...

Tips for setting the scroll back to the top when switching between pages in quasar

Whenever a qlist item is clicked by the user, it redirects to another page. However, the scrolled position from the previous page is retained and not set to the top. This means that the user has to manually scroll back to the top to view the contents of th ...

It appears that the functions in linqts are not clearly defined

Currently, I am in the process of learning Angular4 and facing challenges with getting linqts to function properly. Within my room-list.component.ts file, I include it in this manner: import { List } from 'linqts'; A few lines below, I have my ...

Utilizing Global Variables and Passing Values in Ionic 3

It seems like my issue is rather straightforward, but there is definitely something eluding me. After logging in, I need to store a TOKEN for HTTP requests in a global variable. Upon successful login, the HTTP get method returns an object with the HTTP co ...

Tips for enabling autofocus in mat-select列表。

I am working on an angular project where I am using Angular Material and a mat-select element. In my form, the mat-select is the first element, and I want to set auto-focus on it when the page loads. However, I have been facing some difficulties achieving ...

TypeORM's one-to-many relationship alters the primary entity once the relationship has been established

When working on my side project, I decided to implement a friend request system using NestJS + TypeORM for the backend. However, I encountered a peculiar issue where every time I tried to associate a Friend entity with a specific user, the target field of ...

The specified field type of Int! was not included in the input

I encountered a GraphQL error that states: "Field JobInput.salarys of required type Int! was not provided." While working on my mutation, I have declared three variables and I'm unsure if the syntax "salarys: number;" is correct. Can someone please c ...

Encountered a higher number of hooks rendered compared to the previous render error on a component without any conditional hook usage

Within my codebase, I have a component that is responsible for rendering a clickable link to initiate a file upload process. import { gql, useLazyQuery, useMutation } from '@apollo/client'; import { useEffect, useState } from 'react'; i ...

Angular is used to call a function that captures a specific div and then waits for the capture to be completed before

I'm facing a challenge where I need to handle the capturing of a div using a method called capture() within another method. Take a look at the code snippet below: theimage; // declaring the variable callcapture() { // perform certain actions t ...

Setting a property with a generic type array: Tips and tricks

Currently, I am working on implementing a select component that can accept an array of any type. However, I am facing some challenges in defining the generic and where to specify it. My project involves using <script setup> with TypeScript. Here is ...

Utilizing Angular 16 to Link Component Input with Parent Route Parameter

Picture a scenario where there is a component (some.component.ts) in Angular 16 that retrieves the value for its foo property from activeRoute, specifically from the parent route. Take a look at the code snippet below: @Input() foo!: string; constructor(p ...

Defining a JSON file interface in Angular to populate a dropdown box with dependencies

I've embarked on an exciting project to develop a cascading dropdown box filter, and it's proving to be quite challenging. I'm taking it step by step to ensure clarity. I have obtained a JSON file containing the data required to populate de ...

Analyzing different kinds of inputs received by a function

Let's say we have the following abstractions for fetching data from an API: Data storage class class DataItem<T> { data?: T | null } Query function function queryData ( fn: () => Promise<any> item: DataItem<any> ...

Guide on toggling mat-checkbox according to API feedback in Angular 6

Just starting out with angular 6 and I'm attempting to toggle the mat-checkbox based on the API response. However, I seem to be having trouble. All the checkboxes are showing as checked even when the API response is false. <div class="col-sm-12" ...